├── .gitignore ├── .goreleaser.yml ├── .travis.yml ├── LICENSE ├── README.md ├── VERSION ├── api ├── api.go ├── build.go ├── builder.go ├── create.go ├── create_test.go ├── imports.go ├── install.go ├── install_test.go ├── legacy.go ├── list.go ├── list_test.go ├── project.go ├── shim.go ├── update.go └── util.go ├── cmd └── flogo │ ├── gen │ └── version.go │ ├── imports.go │ └── main.go ├── commands ├── build.go ├── create.go ├── imports.go ├── install.go ├── list.go ├── plugin.go ├── pluginhelper.go ├── root.go └── update.go ├── common ├── build.go ├── env.go ├── plugin.go └── project.go ├── docs ├── commands.md └── plugins.md ├── examples └── simple-plugin │ ├── go.mod │ └── myplugin.go ├── go.mod ├── go.sum ├── test └── flogo.json ├── util ├── app.go ├── ast.go ├── contrib.go ├── engine.go ├── env.go ├── file.go ├── flogo.go ├── flogo_test.go ├── gosrchelper.go ├── gosrchelper_test.go ├── imports.go ├── mod.go └── version.go └── version.go /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ## OS X Resources 3 | .DS_Store 4 | .vscode/ 5 | 6 | # Binaries 7 | *.exe 8 | *.test 9 | *.prof 10 | 11 | 12 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 13 | *.o 14 | *.a 15 | *.so 16 | 17 | # Dynamic libs 18 | *.dll 19 | *.so 20 | *.dylib 21 | 22 | # Intellij 23 | 24 | .idea/ 25 | 26 | # cli binary 27 | ./flogo 28 | dist 29 | 30 | go.mod.build 31 | 32 | cmd/flogo/currentversion.go 33 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod download 4 | - go generate ./... 5 | builds: 6 | - goos: 7 | - darwin 8 | - linux 9 | - windows 10 | goarch: 11 | - amd64 12 | env: 13 | - CGO_ENABLED=0 14 | - GO111MODULE=on 15 | main: ./cmd/flogo/ 16 | binary: flogo 17 | archives: 18 | - format: binary 19 | replacements: 20 | darwin: osx 21 | linux: linux 22 | windows: win 23 | amd64: x86_64 24 | checksum: 25 | name_template: 'checksums.txt' 26 | changelog: 27 | sort: asc 28 | filters: 29 | exclude: 30 | - '^docs:' 31 | - '^test:' 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.12.x 4 | os: 5 | - linux 6 | env: 7 | - GO111MODULE=on 8 | install: skip 9 | script: 10 | - GO111MODULE=off go get ./... # to populate $GOPATH/src for tests 11 | - go build ./... 12 | - go test ./... 13 | deploy: 14 | - provider: script 15 | skip_cleanup: true 16 | script: curl -sL https://git.io/goreleaser | bash 17 | on: 18 | tags: true 19 | condition: $TRAVIS_OS_NAME = linux 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, TIBCO Software, Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of TIBCO Software Inc. nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | Serverless functions and edge microservices made painless 7 |

8 | 9 |

10 | 11 | 12 | 13 | 14 |

15 | 16 |

17 | Installation | Getting Started | Documentation | Contributing | License 18 |

19 | 20 |
21 | Project Flogo is an open source framework to simplify building efficient & modern serverless functions and edge microservices and this is the cli that makes it all happen. 22 | 23 | FLOGO CLI 24 | ====================== 25 | 26 | The Flogo CLI is the primary tool to use when working with a Flogo application. It is used to create, modify and build Flogo applications 27 | ## Installation 28 | ### Prerequisites 29 | To get started with the Flogo CLI you'll need to have a few things 30 | * The Go programming language version 1.11 or later should be [installed](https://golang.org/doc/install). 31 | * In order to simplify dependency management, we're using **go mod**. For more information on **go mod**, visit the [Go Modules Wiki](https://github.com/golang/go/wiki/Modules). 32 | 33 | ### Install the cli 34 | To install the CLI, simply open a terminal and enter the below command 35 | 36 | ``` 37 | $ go install github.com/project-flogo/cli/...@latest 38 | ``` 39 | 40 | ### Build the CLI from source 41 | You can build the cli from source code as well, which is convenient if you're developing new features for it! To do that, follow these easy steps 42 | 43 | ```bash 44 | # Get the flogo CLI from GitHub 45 | $ git clone https://github.com/project-flogo/cli.git 46 | 47 | # Go to the directory 48 | $ cd cli 49 | 50 | # Optionally check out the branch you want to use 51 | $ git checkout test_branch 52 | 53 | # Run the install command 54 | $ go install ./... 55 | ``` 56 | 57 | ## Getting started 58 | Getting started should be easy and fun, and so is getting started with the Flogo cli. 59 | 60 | First, create a file called `flogo.json` and with the below content (which is a simple app with an [HTTP trigger](https://tibcosoftware.github.io/flogo/development/webui/triggers/rest/)) 61 | 62 | ```json 63 | { 64 | "name": "SampleApp", 65 | "type": "flogo:app", 66 | "version": "0.0.1", 67 | "appModel": "1.1.0", 68 | "imports": [ 69 | "github.com/project-flogo/contrib/trigger/rest", 70 | "github.com/project-flogo/flow", 71 | "github.com/project-flogo/contrib/activity/log" 72 | ], 73 | "triggers": [ 74 | { 75 | "id": "receive_http_message", 76 | "ref": "#rest", 77 | "name": "Receive HTTP Message", 78 | "description": "Simple REST Trigger", 79 | "settings": { 80 | "port": 9233 81 | }, 82 | "handlers": [ 83 | { 84 | "settings": { 85 | "method": "GET", 86 | "path": "/test" 87 | }, 88 | "action": { 89 | "ref": "#flow", 90 | "settings": { 91 | "flowURI": "res://flow:sample_flow" 92 | } 93 | } 94 | } 95 | ] 96 | } 97 | ], 98 | "resources": [ 99 | { 100 | "id": "flow:sample_flow", 101 | "data": { 102 | "name": "SampleFlow", 103 | "tasks": [ 104 | { 105 | "id": "log_message", 106 | "name": "Log Message", 107 | "description": "Simple Log Activity", 108 | "activity": { 109 | "ref": "#log", 110 | "input": { 111 | "message": "Simple Log", 112 | "addDetails": "false" 113 | } 114 | } 115 | } 116 | ] 117 | } 118 | } 119 | ] 120 | } 121 | ``` 122 | 123 | Based on this file we'll create a new flogo app 124 | 125 | ```bash 126 | $ flogo create -f flogo.json myApp 127 | ``` 128 | 129 | From the app folder we can build the executable 130 | 131 | ```bash 132 | $ cd myApp 133 | $ flogo build -e 134 | ``` 135 | 136 | Now that there is an executable we can run it! 137 | 138 | ```bash 139 | $ cd bin 140 | $ ./myApp 141 | ``` 142 | 143 | The above commands will start the REST server and wait for messages to be sent to `http://localhost:9233/test`. To send a message you can use your browser, or a new terminal window and run 144 | 145 | ```bash 146 | $ curl http://localhost:9233/test 147 | ``` 148 | 149 | _For more tutorials check out the [Labs](https://tibcosoftware.github.io/flogo/labs/) section in our documentation_ 150 | 151 | ## Documentation 152 | 153 | There is documentation also available for [CLI Commands](docs/commands.md) and [CLI Plugins](docs/plugins.md). 154 | 155 | ## Contributing 156 | Want to contribute to Project Flogo? We've made it easy, all you need to do is fork the repository you intend to contribute to, make your changes and create a Pull Request! Once the pull request has been created, you'll be prompted to sign the CLA (Contributor License Agreement) online. 157 | 158 | Not sure where to start? No problem, you can browse the Project Flogo repos and look for issues tagged `kind/help-wanted` or `good first issue`. To make this even easier, we've added the links right here too! 159 | * Project Flogo: [kind/help-wanted](https://github.com/TIBCOSoftware/flogo/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) and [good first issue](https://github.com/TIBCOSoftware/flogo/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) 160 | * flogo cli: [kind/help-wanted](https://github.com/project-flogo/cli/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) and [good first issue](https://github.com/project-flogo/cli/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) 161 | * flogo core: [kind/help-wanted](https://github.com/project-flogo/core/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) and [good first issue](https://github.com/project-flogo/core/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) 162 | * flogo contrib: [kind/help-wanted](https://github.com/project-flogo/contrib/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) and [good first issue](https://github.com/project-flogo/contrib/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) 163 | 164 | Another great way to contribute to Project Flogo is to check [flogo-contrib](https://github.com/project-flogo/contrib). That repository contains some basic contributions, such as activities, triggers, etc. Perhaps there is something missing? Create a new activity or trigger or fix a bug in an existing activity or trigger. 165 | 166 | If you have any questions, feel free to post an issue and tag it as a question, email flogo-oss@tibco.com or chat with the team and community: 167 | 168 | * The [project-flogo/Lobby](https://gitter.im/project-flogo/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link) Gitter channel should be used for general discussions, start here for all things Flogo! 169 | * The [project-flogo/developers](https://gitter.im/project-flogo/developers?utm_source=share-link&utm_medium=link&utm_campaign=share-link) Gitter channel should be used for developer/contributor focused conversations. 170 | 171 | For additional details, refer to the [Contribution Guidelines](https://github.com/TIBCOSoftware/flogo/blob/master/CONTRIBUTING.md). 172 | 173 | ## License 174 | Flogo source code in [this](https://github.com/project-flogo/cli) repository is under a BSD-style license, refer to [LICENSE](https://github.com/project-flogo/cli/blob/master/LICENSE) 175 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | v0.11.1 -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/project-flogo/cli/util" 5 | ) 6 | 7 | var verbose = false 8 | 9 | func SetVerbose(enable bool) { 10 | verbose = enable 11 | util.SetVerbose(enable) 12 | } 13 | 14 | func Verbose() bool { 15 | return verbose 16 | } 17 | 18 | //TODO use a logger like struct for API that can be used to log or console output 19 | -------------------------------------------------------------------------------- /api/build.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "go/parser" 6 | "go/printer" 7 | "go/token" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "github.com/project-flogo/cli/common" 14 | "github.com/project-flogo/cli/util" 15 | ) 16 | 17 | const ( 18 | fileEmbeddedAppGo string = "embeddedapp.go" 19 | ) 20 | 21 | func BuildProject(project common.AppProject, options common.BuildOptions) error { 22 | 23 | err := project.DepManager().AddReplacedContribForBuild() 24 | if err != nil { 25 | return err 26 | } 27 | 28 | buildPreProcessors := common.BuildPreProcessors() 29 | 30 | if len(buildPreProcessors) > 0 { 31 | for _, processor := range buildPreProcessors { 32 | err = processor.DoPreProcessing(project,options) 33 | if err != nil { 34 | return err 35 | } 36 | } 37 | } 38 | 39 | var builder common.Builder 40 | embedConfig := options.EmbedConfig 41 | 42 | if options.Shim != "" { 43 | builder = &ShimBuilder{shim:options.Shim} 44 | embedConfig = true 45 | } else { 46 | builder = &AppBuilder{} 47 | } 48 | 49 | if embedConfig { 50 | err = createEmbeddedAppGoFile(project) 51 | if err != nil { 52 | return err 53 | } 54 | } else { 55 | err = cleanupEmbeddedAppGoFile(project) 56 | if err != nil { 57 | return err 58 | } 59 | } 60 | 61 | if options.OptimizeImports { 62 | if Verbose() { 63 | fmt.Println("Optimizing imports...") 64 | } 65 | err := optimizeImports(project) 66 | defer restoreImports(project) 67 | 68 | if err != nil { 69 | return err 70 | } 71 | } 72 | 73 | err = builder.Build(project) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | buildPostProcessors := common.BuildPostProcessors() 79 | 80 | if len(buildPostProcessors) > 0 { 81 | for _, processor := range buildPostProcessors { 82 | err = processor.DoPostProcessing(project) 83 | if err != nil { 84 | return err 85 | } 86 | } 87 | } 88 | 89 | return nil 90 | } 91 | 92 | func cleanupEmbeddedAppGoFile(project common.AppProject) error { 93 | embedSrcPath := filepath.Join(project.SrcDir(), fileEmbeddedAppGo) 94 | 95 | if _, err := os.Stat(embedSrcPath); err == nil { 96 | if Verbose() { 97 | fmt.Println("Removing embed configuration") 98 | } 99 | err = os.Remove(embedSrcPath) 100 | if err != nil { 101 | return err 102 | } 103 | } 104 | return nil 105 | } 106 | 107 | func createEmbeddedAppGoFile(project common.AppProject) error { 108 | 109 | embedSrcPath := filepath.Join(project.SrcDir(), fileEmbeddedAppGo) 110 | 111 | if Verbose() { 112 | fmt.Println("Embedding configuration in application...") 113 | } 114 | 115 | buf, err := ioutil.ReadFile(filepath.Join(project.Dir(), fileFlogoJson)) 116 | if err != nil { 117 | return err 118 | } 119 | flogoJSON := string(buf) 120 | 121 | tplFile := tplEmbeddedAppGoFile 122 | if !isNewMain(project) { 123 | tplFile = tplEmbeddedAppOldGoFile 124 | } 125 | 126 | engineJSON := "" 127 | 128 | if util.FileExists(filepath.Join(project.Dir(), fileEngineJson)) { 129 | buf, err = ioutil.ReadFile(filepath.Join(project.Dir(), fileEngineJson)) 130 | if err != nil { 131 | return err 132 | } 133 | 134 | engineJSON = string(buf) 135 | } 136 | 137 | data := struct { 138 | FlogoJSON string 139 | EngineJSON string 140 | }{ 141 | flogoJSON, 142 | engineJSON, 143 | } 144 | 145 | f, err := os.Create(embedSrcPath) 146 | if err != nil { 147 | return err 148 | } 149 | RenderTemplate(f, tplFile, &data) 150 | _ = f.Close() 151 | 152 | return nil 153 | } 154 | 155 | func isNewMain(project common.AppProject) bool { 156 | mainGo := filepath.Join(project.SrcDir(), fileMainGo) 157 | buf, err := ioutil.ReadFile(mainGo) 158 | if err == nil { 159 | mainCode := string(buf) 160 | return strings.Contains(mainCode, "cfgEngine") 161 | 162 | } 163 | 164 | return false 165 | } 166 | 167 | 168 | var tplEmbeddedAppGoFile = `// Do not change this file, it has been generated using flogo-cli 169 | // If you change it and rebuild the application your changes might get lost 170 | package main 171 | 172 | // embedded flogo app descriptor file 173 | const flogoJSON string = ` + "`{{.FlogoJSON}}`" + ` 174 | const engineJSON string = ` + "`{{.EngineJSON}}`" + ` 175 | 176 | func init () { 177 | cfgJson = flogoJSON 178 | cfgEngine = engineJSON 179 | } 180 | ` 181 | 182 | var tplEmbeddedAppOldGoFile = `// Do not change this file, it has been generated using flogo-cli 183 | // If you change it and rebuild the application your changes might get lost 184 | package main 185 | 186 | // embedded flogo app descriptor file 187 | const flogoJSON string = ` + "`{{.FlogoJSON}}`" + ` 188 | 189 | func init () { 190 | cfgJson = flogoJSON 191 | } 192 | ` 193 | 194 | func initMain(project common.AppProject, backupMain bool) error { 195 | 196 | //backup main if it exists 197 | mainGo := filepath.Join(project.SrcDir(), fileMainGo) 198 | mainGoBak := filepath.Join(project.SrcDir(), fileMainGo+".bak") 199 | 200 | if backupMain { 201 | if _, err := os.Stat(mainGo); err == nil { 202 | err = os.Rename(mainGo, mainGoBak) 203 | if err != nil { 204 | return err 205 | } 206 | } else if _, err := os.Stat(mainGoBak); err != nil { 207 | return fmt.Errorf("project corrupt, main missing") 208 | } 209 | } else { 210 | if _, err := os.Stat(mainGoBak); err == nil { 211 | err = os.Rename(mainGoBak, mainGo) 212 | if err != nil { 213 | return err 214 | } 215 | } else if _, err := os.Stat(mainGo); err != nil { 216 | return fmt.Errorf("project corrupt, main missing") 217 | } 218 | } 219 | 220 | return nil 221 | } 222 | 223 | func optimizeImports(project common.AppProject) error { 224 | 225 | appImports, err := util.GetAppImports(filepath.Join(project.Dir(), fileFlogoJson), project.DepManager(), true) 226 | if err != nil { 227 | return err 228 | } 229 | 230 | var unused []util.Import 231 | appImports.GetAllImports() 232 | for _, impDetails := range appImports.GetAllImportDetails() { 233 | if !impDetails.Referenced() && impDetails.IsCoreContrib() { 234 | unused = append(unused, impDetails.Imp) 235 | } 236 | } 237 | 238 | importsFile := filepath.Join(project.SrcDir(), fileImportsGo) 239 | importsFileOrig := filepath.Join(project.SrcDir(), fileImportsGo+".orig") 240 | 241 | err = util.CopyFile(importsFile, importsFileOrig) 242 | if err != nil { 243 | return err 244 | } 245 | 246 | fset := token.NewFileSet() 247 | file, err := parser.ParseFile(fset, importsFile, nil, parser.ImportsOnly) 248 | if err != nil { 249 | return err 250 | } 251 | 252 | for _, i := range unused { 253 | if Verbose() { 254 | fmt.Printf(" Removing Import: %s\n", i.GoImportPath()) 255 | } 256 | util.DeleteImport(fset, file, i.GoImportPath()) 257 | } 258 | 259 | f, err := os.Create(importsFile) 260 | defer f.Close() 261 | if err := printer.Fprint(f, fset, file); err != nil { 262 | return err 263 | } 264 | 265 | return nil 266 | } 267 | 268 | func restoreImports(project common.AppProject) { 269 | 270 | importsFile := filepath.Join(project.SrcDir(), fileImportsGo) 271 | importsFileOrig := filepath.Join(project.SrcDir(), fileImportsGo+".orig") 272 | 273 | if _, err := os.Stat(importsFileOrig); err == nil { 274 | err = util.CopyFile(importsFileOrig, importsFile) 275 | if err != nil { 276 | fmt.Fprintf(os.Stderr, "Error restoring imports file '%s': %v\n", importsFile, err) 277 | return 278 | } 279 | 280 | var err = os.Remove(importsFileOrig) 281 | if err != nil { 282 | fmt.Fprintf(os.Stderr, "Error removing backup imports file '%s': %v\n", importsFileOrig, err) 283 | fmt.Fprintf(os.Stderr, "Manually remove backup imports file '%s'\n", importsFileOrig) 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /api/builder.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | 8 | "github.com/project-flogo/cli/common" 9 | "github.com/project-flogo/cli/util" 10 | ) 11 | 12 | type AppBuilder struct { 13 | 14 | } 15 | 16 | func (*AppBuilder) Build(project common.AppProject) error { 17 | 18 | err := restoreMain(project) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | err = simpleGoBuild(project) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | return nil 29 | } 30 | 31 | 32 | func simpleGoBuild(project common.AppProject) error { 33 | if _, err := os.Stat(project.BinDir()); err != nil { 34 | if Verbose() { 35 | fmt.Println("Creating 'bin' directory") 36 | } 37 | err = os.MkdirAll(project.BinDir(), os.ModePerm) 38 | if err != nil { 39 | return err 40 | } 41 | } 42 | 43 | if Verbose() { 44 | fmt.Println("Performing 'go build'...") 45 | } 46 | 47 | err := util.ExecCmd(exec.Command("go", "build", "-o", project.Executable()), project.SrcDir()) 48 | if err != nil { 49 | fmt.Println("Error in building", project.SrcDir()) 50 | return err 51 | } 52 | 53 | return nil 54 | } -------------------------------------------------------------------------------- /api/create.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | "os/exec" 11 | 12 | "github.com/project-flogo/cli/common" 13 | "github.com/project-flogo/cli/util" 14 | ) 15 | 16 | var fileSampleEngineMain = filepath.Join("examples", "engine", "main.go") 17 | 18 | func CreateProject(basePath, appName, appCfgPath, coreVersion string) (common.AppProject, error) { 19 | 20 | var err error 21 | var appJson string 22 | 23 | if appCfgPath != "" { 24 | 25 | if util.IsRemote(appCfgPath) { 26 | 27 | appJson, err = util.LoadRemoteFile(appCfgPath) 28 | if err != nil { 29 | return nil, fmt.Errorf("unable to load remote app file '%s' - %s", appCfgPath, err.Error()) 30 | } 31 | } else { 32 | appJson, err = util.LoadLocalFile(appCfgPath) 33 | if err != nil { 34 | return nil, fmt.Errorf("unable to load app file '%s' - %s", appCfgPath, err.Error()) 35 | } 36 | } 37 | } else { 38 | if len(appName) == 0 { 39 | return nil, fmt.Errorf("app name not specified") 40 | } 41 | } 42 | 43 | appName, err = getAppName(appName, appJson) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | fmt.Printf("Creating Flogo App: %s\n", appName) 49 | 50 | appDir, err := createAppDirectory(basePath, appName) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | srcDir := filepath.Join(appDir, "src") 56 | dm := util.NewDepManager(srcDir) 57 | 58 | if Verbose() { 59 | fmt.Printf("Setting up app directory: %s\n", appDir) 60 | } 61 | 62 | err = setupAppDirectory(dm, appDir, coreVersion) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | if Verbose() { 68 | if appJson == "" { 69 | fmt.Println("Adding sample flogo.json") 70 | } 71 | } 72 | err = createAppJson(dm, appDir, appName, appJson) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | err = createMain(dm, appDir) 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | project := NewAppProject(appDir) 83 | 84 | if Verbose() { 85 | fmt.Println("Importing Dependencies...") 86 | } 87 | 88 | err = importDependencies(project) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | if Verbose() { 94 | fmt.Printf("Created App: %s\n", appName) 95 | } 96 | 97 | return project, nil 98 | } 99 | 100 | // createAppDirectory creates the flogo app directory 101 | func createAppDirectory(basePath, appName string) (string, error) { 102 | 103 | var err error 104 | 105 | if basePath == "." { 106 | basePath, err = os.Getwd() 107 | if err != nil { 108 | return "", err 109 | } 110 | } 111 | 112 | appPath := filepath.Join(basePath, appName) 113 | err = os.Mkdir(appPath, os.ModePerm) 114 | if err != nil { 115 | return "", err 116 | } 117 | 118 | return appPath, nil 119 | } 120 | 121 | //setupAppDirectory sets up the flogo app directory 122 | func setupAppDirectory(dm util.DepManager, appPath, coreVersion string) error { 123 | 124 | err := os.Mkdir(filepath.Join(appPath, dirBin), os.ModePerm) 125 | if err != nil { 126 | return err 127 | } 128 | 129 | srcDir := filepath.Join(appPath, dirSrc) 130 | err = os.Mkdir(srcDir, os.ModePerm) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | _, err = os.Create(filepath.Join(srcDir, fileImportsGo)) 136 | if err != nil { 137 | return err 138 | } 139 | err = ioutil.WriteFile(filepath.Join(srcDir, fileImportsGo), []byte("package main\n"), 0644) 140 | if err != nil { 141 | return err 142 | } 143 | 144 | err = dm.Init() 145 | if err != nil { 146 | return err 147 | } 148 | 149 | flogoCoreImport := util.NewFlogoImport(flogoCoreRepo, "", coreVersion, "") 150 | 151 | //todo get the actual version installed from the go.mod file 152 | if coreVersion == "" { 153 | fmt.Printf("Installing: %s@latest\n", flogoCoreImport.CanonicalImport()) 154 | } else { 155 | fmt.Printf("Installing: %s\n", flogoCoreImport.CanonicalImport()) 156 | } 157 | 158 | // add & fetch the core library 159 | err = dm.AddDependency(flogoCoreImport) 160 | if err != nil { 161 | return err 162 | } 163 | 164 | return nil 165 | } 166 | 167 | // createAppJson create the flogo app json 168 | func createAppJson(dm util.DepManager, appDir, appName, appJson string) error { 169 | 170 | updatedJson, err := getAndUpdateAppJson(dm, appName, appJson) 171 | if err != nil { 172 | return err 173 | } 174 | 175 | err = ioutil.WriteFile(filepath.Join(appDir, fileFlogoJson), []byte(updatedJson), 0644) 176 | if err != nil { 177 | return err 178 | } 179 | 180 | return nil 181 | } 182 | 183 | // importDependencies import all dependencies 184 | func importDependencies(project common.AppProject) error { 185 | 186 | ai, err := util.GetAppImports(filepath.Join(project.Dir(), fileFlogoJson), project.DepManager(), true) 187 | if err != nil { 188 | return err 189 | } 190 | 191 | imports := ai.GetAllImports() 192 | 193 | if len(imports) == 0 { 194 | return nil 195 | } 196 | 197 | err = project.AddImports(true, false, imports...) 198 | if err != nil { 199 | return err 200 | } 201 | 202 | legacySupportRequired := false 203 | 204 | for _, details := range ai.GetAllImportDetails() { 205 | 206 | path, err := project.GetPath(details.Imp) 207 | if err != nil { 208 | return err 209 | } 210 | 211 | desc, err := util.GetContribDescriptor(path) 212 | 213 | if err != nil { 214 | return err 215 | } 216 | 217 | if desc != nil { 218 | 219 | cType := desc.GetContribType() 220 | if desc.IsLegacy { 221 | legacySupportRequired = true 222 | cType = "legacy " + desc.GetContribType() 223 | err := CreateLegacyMetadata(path, desc.GetContribType(), details.Imp.GoImportPath()) 224 | if err != nil { 225 | return err 226 | } 227 | } 228 | 229 | fmt.Printf("Installed %s: %s\n", cType, details.Imp) 230 | //instStr := fmt.Sprintf("Installed %s:", cType) 231 | //fmt.Printf("%-20s %s\n", instStr, imp) 232 | } 233 | } 234 | 235 | if Verbose() { 236 | fmt.Printf("Tidying go mod...") 237 | } 238 | 239 | err = util.ExecCmd(exec.Command("go", "mod", "tidy"), project.SrcDir()) 240 | if err != nil { 241 | fmt.Printf("Failed to clean deps: %s\n", err) 242 | } 243 | 244 | if legacySupportRequired { 245 | err := InstallLegacySupport(project) 246 | return err 247 | } 248 | 249 | return nil 250 | } 251 | 252 | func createMain(dm util.DepManager, appDir string) error { 253 | 254 | flogoCoreImport, err := util.NewFlogoImportFromPath(flogoCoreRepo) 255 | if err != nil { 256 | return err 257 | } 258 | 259 | corePath, err := dm.GetPath(flogoCoreImport) 260 | if err != nil { 261 | return err 262 | } 263 | 264 | bytes, err := ioutil.ReadFile(filepath.Join(corePath, fileSampleEngineMain)) 265 | if err != nil { 266 | return err 267 | } 268 | 269 | err = ioutil.WriteFile(filepath.Join(appDir, dirSrc, fileMainGo), bytes, 0644) 270 | if err != nil { 271 | return err 272 | } 273 | 274 | return nil 275 | } 276 | 277 | func getAndUpdateAppJson(dm util.DepManager, appName, appJson string) (string, error) { 278 | 279 | if len(appJson) == 0 { 280 | appJson = emptyFlogoJson 281 | } 282 | 283 | descriptor, err := util.ParseAppDescriptor(appJson) 284 | if err != nil { 285 | return "", err 286 | } 287 | 288 | if appName != "" { 289 | // override the application name 290 | 291 | altJson := strings.Replace(appJson, `"`+descriptor.Name+`"`, `"`+appName+`"`, 1) 292 | altDescriptor, err := util.ParseAppDescriptor(altJson) 293 | 294 | //see if we can get away with simple replace so we don't reorder the existing json 295 | if err == nil && altDescriptor.Name == appName { 296 | appJson = altJson 297 | } else { 298 | //simple replace didn't work so we have to unmarshal & re-marshal the supplied json 299 | var appObj map[string]interface{} 300 | err := json.Unmarshal([]byte(appJson), &appObj) 301 | if err != nil { 302 | return "", err 303 | } 304 | 305 | appObj["name"] = appName 306 | 307 | updApp, err := json.MarshalIndent(appObj, "", " ") 308 | if err != nil { 309 | return "", err 310 | } 311 | appJson = string(updApp) 312 | } 313 | 314 | descriptor.Name = appName 315 | } else { 316 | appName = descriptor.Name 317 | } 318 | 319 | return appJson, nil 320 | } 321 | 322 | func getAppName(appName, appJson string) (string, error) { 323 | 324 | if appJson != "" && appName == "" { 325 | descriptor, err := util.ParseAppDescriptor(appJson) 326 | if err != nil { 327 | return "", err 328 | } 329 | 330 | return descriptor.Name, nil 331 | } 332 | 333 | return appName, nil 334 | } 335 | func GetTempDir() (string, error) { 336 | 337 | tempDir, err := ioutil.TempDir("", "flogo") 338 | if err != nil { 339 | return "", err 340 | } 341 | return tempDir, nil 342 | } 343 | 344 | var emptyFlogoJson = ` 345 | { 346 | "name": "{{.AppName}}", 347 | "type": "flogo:app", 348 | "version": "0.0.1", 349 | "description": "My Flogo Application Description", 350 | "appModel": "1.1.0", 351 | "imports": [], 352 | "triggers": [], 353 | "resources":[] 354 | } 355 | ` 356 | -------------------------------------------------------------------------------- /api/create_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | var jsonString = `{ 14 | "name": "_APP_NAME_", 15 | "type": "flogo:app", 16 | "version": "0.0.1", 17 | "description": "My flogo application description", 18 | "appModel": "1.0.0", 19 | "imports": [ 20 | "github.com/TIBCOSoftware/flogo-contrib/activity/log", 21 | "github.com/project-flogo/contrib/trigger/rest", 22 | "github.com/project-flogo/flow" 23 | ], 24 | "triggers": [ 25 | { 26 | "id": "my_rest_trigger", 27 | "type": "rest", 28 | "settings": { 29 | "port": "8888" 30 | }, 31 | "handlers": [ 32 | { 33 | "settings": { 34 | "method": "GET", 35 | "path": "/test/:val" 36 | }, 37 | "actions": [ 38 | { 39 | "type": "flow", 40 | "settings": { 41 | "flowURI": "res://flow:simple_flow" 42 | }, 43 | "input": { 44 | "in": "=$.pathParams.val" 45 | } 46 | } 47 | ] 48 | } 49 | ] 50 | } 51 | ], 52 | "resources": [ 53 | { 54 | "id": "flow:simple_flow", 55 | "data": { 56 | "name": "simple_flow", 57 | "metadata": { 58 | "input": [ 59 | { "name": "in", "type": "string", "value": "test" } 60 | ], 61 | "output": [ 62 | { "name": "out", "type": "string" } 63 | ] 64 | }, 65 | "tasks": [ 66 | { 67 | "id": "log", 68 | "name": "Log Message", 69 | "activity": { 70 | "type": "log", 71 | "input": { 72 | "message": "=$flow.in", 73 | "flowInfo": "false", 74 | "addToFlow": "false" 75 | } 76 | } 77 | } 78 | ], 79 | "links": [] 80 | } 81 | } 82 | ] 83 | } 84 | ` 85 | 86 | type TestEnv struct { 87 | currentDir string 88 | } 89 | 90 | func (t *TestEnv) getTestwd() (dir string, err error) { 91 | return t.currentDir, nil 92 | } 93 | 94 | func (t *TestEnv) cleanup() { 95 | 96 | os.RemoveAll(t.currentDir) 97 | } 98 | 99 | func TestCmdCreate_noflag(t *testing.T) { 100 | t.Log("Testing simple creation of project") 101 | 102 | tempDir, _ := GetTempDir() 103 | testEnv := &TestEnv{currentDir: tempDir} 104 | 105 | defer testEnv.cleanup() 106 | 107 | t.Logf("Current dir '%s'", testEnv.currentDir) 108 | _, err := CreateProject(testEnv.currentDir, "myApp", "", "") 109 | assert.Equal(t, nil, err) 110 | 111 | _, err = os.Stat(filepath.Join(tempDir, "myApp", "src", "go.mod")) 112 | 113 | assert.Equal(t, nil, err) 114 | _, err = os.Stat(filepath.Join(tempDir, "myApp", "flogo.json")) 115 | 116 | assert.Equal(t, nil, err) 117 | 118 | _, err = os.Stat(filepath.Join(tempDir, "myApp", "src", "main.go")) 119 | assert.Equal(t, nil, err) 120 | } 121 | 122 | func TestCmdCreate_flag(t *testing.T) { 123 | t.Log("Testing creation of project while the file is provided") 124 | 125 | tempDir, err := ioutil.TempDir("", "test") 126 | if err != nil { 127 | t.Fatal(err) 128 | } 129 | 130 | testEnv := &TestEnv{currentDir: tempDir} 131 | 132 | defer testEnv.cleanup() 133 | 134 | t.Logf("Current dir '%s'", testEnv.currentDir) 135 | os.Chdir(testEnv.currentDir) 136 | file, err := os.Create("flogo.json") 137 | if err != nil { 138 | t.Fatal(err) 139 | assert.Equal(t, true, false) 140 | } 141 | defer file.Close() 142 | fmt.Fprintf(file, jsonString) 143 | _, err = CreateProject(testEnv.currentDir, "flogo", "flogo.json", "") 144 | assert.Equal(t, nil, err) 145 | 146 | _, err = os.Stat(filepath.Join(tempDir, "flogo", "src", "go.mod")) 147 | 148 | assert.Equal(t, nil, err) 149 | _, err = os.Stat(filepath.Join(tempDir, "flogo", "flogo.json")) 150 | 151 | assert.Equal(t, nil, err) 152 | 153 | _, err = os.Stat(filepath.Join(tempDir, "flogo", "src", "main.go")) 154 | assert.Equal(t, nil, err) 155 | } 156 | 157 | func TestCmdCreate_masterCore(t *testing.T) { 158 | t.Log("Testing creation of project when the version of core is provided `master`") 159 | 160 | tempDir, err := ioutil.TempDir("", "test") 161 | if err != nil { 162 | t.Fatal(err) 163 | } 164 | 165 | testEnv := &TestEnv{currentDir: tempDir} 166 | 167 | defer testEnv.cleanup() 168 | 169 | t.Logf("Current dir '%s'", testEnv.currentDir) 170 | os.Chdir(testEnv.currentDir) 171 | 172 | _, err = CreateProject(testEnv.currentDir, "myApp", "", "master") 173 | assert.Equal(t, nil, err) 174 | } 175 | 176 | //todo fix this test, unreliable 177 | //func TestCmdCreate_versionCore(t *testing.T) { 178 | // t.Log("Testing creation of project when the version of core is provided `v0.9.0-alpha.4`") 179 | // 180 | // tempDir, err := ioutil.TempDir("", "test") 181 | // if err != nil { 182 | // t.Fatal(err) 183 | // } 184 | // 185 | // testEnv := &TestEnv{currentDir: tempDir} 186 | // 187 | // defer testEnv.cleanup() 188 | // 189 | // t.Logf("Current dir '%s'", testEnv.currentDir) 190 | // os.Chdir(testEnv.currentDir) 191 | // 192 | // _, err = CreateProject(testEnv.currentDir, "myApp", "", "v0.9.0-alpha.4") 193 | // assert.Equal(t, nil, err) 194 | // 195 | // _, err = os.Stat(filepath.Join(tempDir, "myApp", "src", "go.mod")) 196 | // 197 | // assert.Equal(t, nil, err) 198 | // _, err = os.Stat(filepath.Join(tempDir, "myApp", "flogo.json")) 199 | // 200 | // assert.Equal(t, nil, err) 201 | // 202 | // _, err = os.Stat(filepath.Join(tempDir, "myApp", "src", "main.go")) 203 | // assert.Equal(t, nil, err) 204 | // 205 | // data, err1 := ioutil.ReadFile(filepath.Join(tempDir, "myApp", "src", "go.mod")) 206 | // assert.Equal(t, nil, err1) 207 | // 208 | // //todo fix, not a reliable test giving that importing latest of flow which will affect this import 209 | // assert.Equal(t, true, strings.Contains(string(data), "v0.9.0-alpha.4")) 210 | // fmt.Println(string(data)) 211 | // 212 | // appProject := NewAppProject(filepath.Join(testEnv.currentDir, "myApp")) 213 | // 214 | // err = appProject.Validate() 215 | // assert.Nil(t, err) 216 | // 217 | // common.SetCurrentProject(appProject) 218 | // 219 | // err = BuildProject(common.CurrentProject(), BuildOptions{}) 220 | // assert.Nil(t, err) 221 | //} 222 | -------------------------------------------------------------------------------- /api/imports.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/project-flogo/cli/common" 10 | "github.com/project-flogo/cli/util" 11 | "github.com/project-flogo/core/app" 12 | ) 13 | 14 | func ListProjectImports(project common.AppProject) error { 15 | 16 | appImports, err := util.GetAppImports(filepath.Join(project.Dir(), fileFlogoJson), project.DepManager(), false) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | for _, imp := range appImports.GetAllImports() { 22 | 23 | fmt.Fprintf(os.Stdout, " %s\n", imp) 24 | } 25 | 26 | return nil 27 | } 28 | 29 | func SyncProjectImports(project common.AppProject) error { 30 | 31 | appImports, err := util.GetAppImports(filepath.Join(project.Dir(), fileFlogoJson), project.DepManager(), false) 32 | if err != nil { 33 | return err 34 | } 35 | appImportsMap := make(map[string]util.Import) 36 | for _, imp := range appImports.GetAllImports() { 37 | appImportsMap[imp.GoImportPath()] = imp 38 | } 39 | 40 | goImports, err := project.GetGoImports(false) 41 | if err != nil { 42 | return err 43 | } 44 | goImportsMap := make(map[string]util.Import) 45 | for _, imp := range goImports { 46 | goImportsMap[imp.GoImportPath()] = imp 47 | } 48 | 49 | var toAdd []util.Import 50 | for goPath, imp := range appImportsMap { 51 | if _, ok := goImportsMap[goPath]; !ok { 52 | toAdd = append(toAdd, imp) 53 | if Verbose() { 54 | fmt.Println("Adding missing Go import: ", goPath) 55 | } 56 | } 57 | } 58 | 59 | if util.FileExists(filepath.Join(project.Dir(), fileEngineJson)) { 60 | engineImports, err := util.GetEngineImports(filepath.Join(project.Dir(), fileEngineJson), project.DepManager()) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | engImportsMap := make(map[string]util.Import) 66 | for _, imp := range engineImports.GetAllImports() { 67 | engImportsMap[imp.GoImportPath()] = imp 68 | } 69 | 70 | for goPath, imp := range engImportsMap { 71 | if _, ok := goImportsMap[goPath]; !ok { 72 | toAdd = append(toAdd, imp) 73 | if Verbose() { 74 | fmt.Println("Adding missing Go import: ", goPath) 75 | } 76 | } 77 | } 78 | } 79 | 80 | var toRemove []string 81 | for goPath := range goImportsMap { 82 | if _, ok := appImportsMap[goPath]; !ok { 83 | toRemove = append(toRemove, goPath) 84 | if Verbose() { 85 | fmt.Println("Removing extraneous Go import: ", goPath) 86 | } 87 | } 88 | } 89 | 90 | err = project.RemoveImports(toRemove...) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | err = project.AddImports(false, false, toAdd...) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | return nil 101 | } 102 | 103 | func ResolveProjectImports(project common.AppProject) error { 104 | if Verbose() { 105 | fmt.Fprintln(os.Stdout, "Synchronizing project imports") 106 | } 107 | err := SyncProjectImports(project) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | if Verbose() { 113 | fmt.Fprintln(os.Stdout, "Reading flogo.json") 114 | } 115 | appDescriptor, err := readAppDescriptor(project) 116 | if err != nil { 117 | return err 118 | } 119 | 120 | if Verbose() { 121 | fmt.Fprintln(os.Stdout, "Updating flogo.json import versions") 122 | } 123 | err = updateDescriptorImportVersions(project, appDescriptor) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | if Verbose() { 129 | fmt.Fprintln(os.Stdout, "Saving updated flogo.json") 130 | } 131 | err = writeAppDescriptor(project, appDescriptor) 132 | if err != nil { 133 | return err 134 | } 135 | 136 | return nil 137 | } 138 | 139 | func updateDescriptorImportVersions(project common.AppProject, appDescriptor *app.Config) error { 140 | 141 | goModImports, err := project.DepManager().GetAllImports() 142 | if err != nil { 143 | return err 144 | } 145 | 146 | appImports, err := util.ParseImports(appDescriptor.Imports) 147 | if err != nil { 148 | return err 149 | } 150 | 151 | var result []string 152 | 153 | for _, appImport := range appImports { 154 | 155 | if goModImport, ok := goModImports[appImport.ModulePath()]; ok { 156 | updatedImp := util.NewFlogoImportWithVersion(appImport, goModImport.Version()) 157 | result = append(result, updatedImp.CanonicalImport()) 158 | } else { 159 | //not found, look for import of parent package 160 | for pkg, goModImport := range goModImports { 161 | if strings.Contains(appImport.ModulePath(), pkg) { 162 | updatedImp := util.NewFlogoImportWithVersion(appImport, goModImport.Version()) 163 | result = append(result, updatedImp.CanonicalImport()) 164 | } 165 | } 166 | } 167 | } 168 | 169 | appDescriptor.Imports = result 170 | 171 | return nil 172 | } 173 | 174 | 175 | -------------------------------------------------------------------------------- /api/install.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | 9 | "github.com/project-flogo/cli/common" 10 | "github.com/project-flogo/cli/util" 11 | ) 12 | 13 | func InstallPackage(project common.AppProject, pkg string) error { 14 | 15 | flogoImport, err := util.ParseImport(pkg) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | err = project.AddImports(false, true, flogoImport) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | path, err := project.GetPath(flogoImport) 26 | if Verbose() { 27 | fmt.Println("Installed path", path) 28 | } 29 | if err != nil { 30 | return err 31 | } 32 | 33 | legacySupportRequired := false 34 | desc, err := util.GetContribDescriptor(path) 35 | if desc != nil { 36 | cType := desc.GetContribType() 37 | if desc.IsLegacy { 38 | legacySupportRequired = true 39 | cType = "legacy " + desc.GetContribType() 40 | err := CreateLegacyMetadata(path, desc.GetContribType(), pkg) 41 | if err != nil { 42 | return err 43 | } 44 | } 45 | 46 | fmt.Printf("Installed %s: %s\n", cType, flogoImport) 47 | //instStr := fmt.Sprintf("Installed %s:", cType) 48 | //fmt.Printf("%-20s %s\n", instStr, imp) 49 | } 50 | 51 | if legacySupportRequired { 52 | err := InstallLegacySupport(project) 53 | if err != nil { 54 | return err 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func InstallReplacedPackage(project common.AppProject, replacedPath string, pkg string) error { 62 | 63 | err := project.DepManager().InstallReplacedPkg(pkg, replacedPath) 64 | if err != nil { 65 | return err 66 | } 67 | return InstallPackage(project, pkg+"@v0.0.0") 68 | } 69 | 70 | func InstallContribBundle(project common.AppProject, path string) error { 71 | 72 | file, err := ioutil.ReadFile(path) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | var contribBundleDescriptor util.FlogoContribBundleDescriptor 78 | 79 | err = json.Unmarshal(file, &contribBundleDescriptor) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | for _, contrib := range contribBundleDescriptor.Contribs { 85 | err := InstallPackage(project, contrib) 86 | if err != nil { 87 | fmt.Fprintf(os.Stderr, "Error installing contrib '%s': %s", contrib, err.Error()) 88 | } 89 | } 90 | 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /api/install_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/project-flogo/cli/common" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | var newJsonString = `{ 13 | "name": "temp", 14 | "type": "flogo:app", 15 | "version": "0.0.1", 16 | "description": "My flogo application description", 17 | "appModel": "1.0.0", 18 | "imports": [ 19 | "github.com/project-flogo/flow", 20 | "github.com/skothari-tibco/flogoaztrigger", 21 | "github.com/project-flogo/contrib/activity/actreturn", 22 | "github.com/project-flogo/contrib/activity/log", 23 | "github.com/TIBCOSoftware/flogo-contrib/activity/rest" 24 | ], 25 | "triggers": [ 26 | { 27 | "id": "my_rest_trigger", 28 | "ref": "github.com/skothari-tibco/flogoaztrigger", 29 | "handlers": [ 30 | { 31 | "action": { 32 | "ref": "github.com/project-flogo/flow", 33 | "settings": { 34 | "flowURI": "res://flow:simple_flow" 35 | }, 36 | "input": { 37 | "in": "inputA" 38 | }, 39 | "output" :{ 40 | "out": "=$.out" 41 | } 42 | } 43 | } 44 | ] 45 | } 46 | ], 47 | "resources": [ 48 | { 49 | "id": "flow:simple_flow", 50 | "data": { 51 | "name": "simple_flow", 52 | "metadata": { 53 | "input": [ 54 | { "name": "in", "type": "string", "value": "test" } 55 | ], 56 | "output": [ 57 | { "name": "out", "type": "string" } 58 | ] 59 | }, 60 | "tasks": [ 61 | { 62 | "id": "log", 63 | "name": "Log Message", 64 | "activity": { 65 | "ref": "github.com/project-flogo/contrib/activity/log", 66 | "input": { 67 | "message": "=$flow.in", 68 | "flowInfo": "false", 69 | "addToFlow": "false" 70 | } 71 | } 72 | }, 73 | { 74 | "id" :"return", 75 | "name" : "Activity Return", 76 | "activity":{ 77 | "ref" : "github.com/project-flogo/contrib/activity/actreturn", 78 | "settings":{ 79 | "mappings":{ 80 | "out": "nameA" 81 | } 82 | } 83 | } 84 | } 85 | ], 86 | "links": [ 87 | { 88 | "from":"log", 89 | "to":"return" 90 | } 91 | ] 92 | } 93 | } 94 | ] 95 | } 96 | ` 97 | 98 | func TestInstallLegacyPkg(t *testing.T) { 99 | t.Log("Testing installation of package") 100 | 101 | tempDir, _ := GetTempDir() 102 | 103 | testEnv := &TestEnv{currentDir: tempDir} 104 | 105 | defer testEnv.cleanup() 106 | 107 | t.Logf("Current dir '%s'", testEnv.currentDir) 108 | _ = os.Chdir(testEnv.currentDir) 109 | 110 | _, err := CreateProject(testEnv.currentDir, "myApp", "", "v0.9.2") 111 | 112 | assert.Nil(t, err) 113 | 114 | err = InstallPackage(NewAppProject(filepath.Join(testEnv.currentDir, "myApp")), "github.com/TIBCOSoftware/flogo-contrib/activity/log") 115 | assert.Nil(t, err) 116 | 117 | appProject := NewAppProject(filepath.Join(testEnv.currentDir, "myApp")) 118 | 119 | err = appProject.Validate() 120 | assert.Nil(t, err) 121 | 122 | common.SetCurrentProject(appProject) 123 | 124 | err = BuildProject(common.CurrentProject(), common.BuildOptions{}) 125 | assert.Nil(t, err) 126 | 127 | } 128 | 129 | func TestInstallPkg(t *testing.T) { 130 | t.Log("Testing installation of package") 131 | 132 | tempDir, _ := GetTempDir() 133 | 134 | testEnv := &TestEnv{currentDir: tempDir} 135 | 136 | defer testEnv.cleanup() 137 | 138 | t.Logf("Current dir '%s'", testEnv.currentDir) 139 | _ = os.Chdir(testEnv.currentDir) 140 | 141 | _, err := CreateProject(testEnv.currentDir, "myApp", "", "") 142 | 143 | assert.Nil(t, err) 144 | 145 | err = InstallPackage(NewAppProject(filepath.Join(testEnv.currentDir, "myApp")), "github.com/project-flogo/contrib/activity/noop") 146 | assert.Nil(t, err) 147 | 148 | appProject := NewAppProject(filepath.Join(testEnv.currentDir, "myApp")) 149 | 150 | err = appProject.Validate() 151 | assert.Nil(t, err) 152 | 153 | common.SetCurrentProject(appProject) 154 | 155 | err = BuildProject(common.CurrentProject(), common.BuildOptions{}) 156 | assert.Nil(t, err) 157 | } 158 | 159 | func TestInstallPkgWithVersion(t *testing.T) { 160 | t.Log("Testing installation of package") 161 | 162 | tempDir, _ := GetTempDir() 163 | 164 | testEnv := &TestEnv{currentDir: tempDir} 165 | 166 | defer testEnv.cleanup() 167 | 168 | t.Logf("Current dir '%s'", testEnv.currentDir) 169 | _ = os.Chdir(testEnv.currentDir) 170 | 171 | _, err := CreateProject(testEnv.currentDir, "myApp", "", "") 172 | 173 | assert.Nil(t, err) 174 | 175 | err = InstallPackage(NewAppProject(filepath.Join(testEnv.currentDir, "myApp")), "github.com/project-flogo/contrib/activity/log@v0.9.0") 176 | assert.Nil(t, err) 177 | 178 | appProject := NewAppProject(filepath.Join(testEnv.currentDir, "myApp")) 179 | 180 | err = appProject.Validate() 181 | assert.Nil(t, err) 182 | 183 | common.SetCurrentProject(appProject) 184 | 185 | err = BuildProject(common.CurrentProject(), common.BuildOptions{}) 186 | assert.Nil(t, err) 187 | } 188 | 189 | func TestListPkg(t *testing.T) { 190 | t.Log("Testing listing of packages") 191 | 192 | tempDir, _ := GetTempDir() 193 | 194 | testEnv := &TestEnv{currentDir: tempDir} 195 | 196 | defer testEnv.cleanup() 197 | 198 | t.Logf("Current dir '%s'", testEnv.currentDir) 199 | _ = os.Chdir(testEnv.currentDir) 200 | 201 | _, err := CreateProject(testEnv.currentDir, "myApp", "", "") 202 | 203 | assert.Equal(t, nil, err) 204 | 205 | err = ListContribs(NewAppProject(filepath.Join(testEnv.currentDir, "myApp")), true, "") 206 | assert.Equal(t, nil, err) 207 | 208 | } 209 | -------------------------------------------------------------------------------- /api/legacy.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | //Legacy Helper Functions 4 | import ( 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "text/template" 12 | 13 | "github.com/project-flogo/cli/common" 14 | "github.com/project-flogo/cli/util" 15 | ) 16 | 17 | const ( 18 | pkgLegacySupport = "github.com/project-flogo/legacybridge" 19 | ) 20 | 21 | func InstallLegacySupport(project common.AppProject) error { 22 | //todo make sure we only install once 23 | pkgLegacySupportImport, err := util.NewFlogoImportFromPath(pkgLegacySupport) 24 | if err != nil { 25 | return err 26 | } 27 | err = project.AddImports(false, true, pkgLegacySupportImport) 28 | if err == nil { 29 | fmt.Println("Installed Legacy Support") 30 | } 31 | return err 32 | } 33 | 34 | func CreateLegacyMetadata(path, contribType, contribPkg string) error { 35 | 36 | var mdGoFilePath string 37 | 38 | tplMetadata := "" 39 | 40 | switch contribType { 41 | case "action": 42 | //ignore 43 | return nil 44 | case "trigger": 45 | fmt.Printf("Generating metadata for legacy trigger: %s\n", contribPkg) 46 | mdGoFilePath = filepath.Join(path, "trigger_metadata.go") 47 | tplMetadata = tplTriggerMetadataGoFile 48 | case "activity": 49 | fmt.Printf("Generating metadata for legacy actvity: %s\n", contribPkg) 50 | mdGoFilePath = filepath.Join(path, "activity_metadata.go") 51 | tplMetadata = tplActivityMetadataGoFile 52 | default: 53 | return nil 54 | } 55 | 56 | mdFilePath := filepath.Join(path, contribType+".json") 57 | pkg := filepath.Base(path) 58 | 59 | if idx := strings.Index(pkg, "@"); idx > 0 { 60 | pkg = pkg[:idx] 61 | } 62 | 63 | raw, err := ioutil.ReadFile(mdFilePath) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | info := &struct { 69 | Package string 70 | MetadataJSON string 71 | }{ 72 | Package: pkg, 73 | MetadataJSON: string(raw), 74 | } 75 | 76 | err = os.Chmod(path, 0777) 77 | if err != nil { 78 | return err 79 | } 80 | defer os.Chmod(path, 0555) 81 | 82 | f, err := os.Create(mdGoFilePath) 83 | if err != nil { 84 | return err 85 | } 86 | RenderTemplate(f, tplMetadata, info) 87 | f.Close() 88 | 89 | return nil 90 | } 91 | 92 | var tplActivityMetadataGoFile = `package {{.Package}} 93 | 94 | import ( 95 | "github.com/project-flogo/legacybridge" 96 | "github.com/TIBCOSoftware/flogo-lib/core/activity" 97 | ) 98 | 99 | var jsonMetadata = ` + "`{{.MetadataJSON}}`" + ` 100 | 101 | // init create & register activity 102 | func init() { 103 | md := activity.NewMetadata(jsonMetadata) 104 | legacybridge.RegisterLegacyActivity(NewActivity(md)) 105 | } 106 | ` 107 | 108 | var tplTriggerMetadataGoFile = `package {{.Package}} 109 | 110 | import ( 111 | "github.com/project-flogo/legacybridge" 112 | "github.com/TIBCOSoftware/flogo-lib/core/trigger" 113 | ) 114 | 115 | var jsonMetadata = ` + "`{{.MetadataJSON}}`" + ` 116 | 117 | // init create & register trigger factory 118 | func init() { 119 | md := trigger.NewMetadata(jsonMetadata) 120 | legacybridge.RegisterLegacyTriggerFactory(md.ID, NewFactory(md)) 121 | } 122 | ` 123 | 124 | //RenderTemplate renders the specified template 125 | func RenderTemplate(w io.Writer, text string, data interface{}) { 126 | t := template.New("top") 127 | t.Funcs(template.FuncMap{"trim": strings.TrimSpace}) 128 | template.Must(t.Parse(text)) 129 | if err := t.Execute(w, data); err != nil { 130 | panic(err) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /api/list.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/project-flogo/cli/common" 11 | "github.com/project-flogo/cli/util" 12 | ) 13 | 14 | type ListFilter int 15 | 16 | func ListContribs(project common.AppProject, jsonFormat bool, filter string) error { 17 | 18 | ai, err := util.GetAppImports(filepath.Join(project.Dir(), fileFlogoJson), project.DepManager(), true) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | var specs []*ContribSpec 24 | 25 | for _, details := range ai.GetAllImportDetails() { 26 | 27 | if !includeContrib(details, filter) { 28 | continue 29 | } 30 | 31 | specs = append(specs, getContribSpec(project, details)) 32 | 33 | } 34 | 35 | for _, details := range ai.GetAllImportDetails() { 36 | 37 | if details.ContribDesc == nil { 38 | continue 39 | } 40 | 41 | if details.ContribDesc.Type == "flogo:function" { 42 | specs = append(specs, getContribSpec(project, details)) 43 | } 44 | } 45 | 46 | if len(specs) == 0 { 47 | return nil 48 | } 49 | 50 | if jsonFormat { 51 | resp, err := json.MarshalIndent(specs, "", " ") 52 | if err != nil { 53 | return err 54 | } 55 | 56 | fmt.Fprintf(os.Stdout, "%v \n", string(resp)) 57 | } else { 58 | for _, spec := range specs { 59 | fmt.Println("Contrib: " + spec.Name) 60 | fmt.Println(" Type : " + spec.Type) 61 | if spec.IsLegacy != nil { 62 | fmt.Println(" IsLegacy : true") 63 | } 64 | fmt.Println(" Homepage : " + spec.Homepage) 65 | fmt.Println(" Ref : " + spec.Ref) 66 | fmt.Println(" Path : " + spec.Path) 67 | fmt.Println(" Descriptor : " + spec.Path) 68 | fmt.Println(" Description: " + spec.Description) 69 | fmt.Println() 70 | } 71 | } 72 | 73 | return nil 74 | } 75 | 76 | func includeContrib(details *util.AppImportDetails, filter string) bool { 77 | 78 | if details.IsCoreContrib() { 79 | 80 | switch strings.ToLower(filter) { 81 | case "used": 82 | return details.Referenced() 83 | case "unused": 84 | return !details.Referenced() 85 | default: 86 | return true 87 | } 88 | } 89 | 90 | return false 91 | 92 | } 93 | 94 | type ContribSpec struct { 95 | Name string `json:"name"` 96 | Type string `json:"type"` 97 | Description string `json:"description"` 98 | Homepage string `json:"homepage"` 99 | Ref string `json:"ref"` 100 | Path string `json:"path"` 101 | Descriptor string `json:"descriptor"` 102 | IsLegacy interface{} `json:"isLegacy,omitempty"` 103 | } 104 | 105 | func getContribSpec(project common.AppProject, details *util.AppImportDetails) *ContribSpec { 106 | path, err := project.GetPath(details.Imp) 107 | if err != nil { 108 | return nil 109 | } 110 | 111 | if Verbose() { 112 | fmt.Println("Path of contrib", path, "for contrib", details.Imp) 113 | } 114 | 115 | desc := details.ContribDesc 116 | 117 | spec := &ContribSpec{} 118 | spec.Name = desc.Name 119 | spec.Type = desc.Type 120 | spec.Description = desc.Description 121 | spec.Homepage = desc.Homepage 122 | spec.Ref = details.Imp.ModulePath() 123 | spec.Path = path 124 | 125 | if desc.IsLegacy { 126 | spec.IsLegacy = true 127 | spec.Descriptor = desc.GetContribType() + ".json" 128 | } else { 129 | spec.Descriptor = "descriptor.json" 130 | } 131 | 132 | return spec 133 | } 134 | func ListOrphanedRefs(project common.AppProject, jsonFormat bool) error { 135 | 136 | ai, err := util.GetAppImports(filepath.Join(project.Dir(), fileFlogoJson), project.DepManager(), true) 137 | if err != nil { 138 | return err 139 | } 140 | 141 | orphaned := ai.GetOrphanedReferences() 142 | 143 | if jsonFormat { 144 | resp, err := json.MarshalIndent(orphaned, "", " ") 145 | if err != nil { 146 | return err 147 | } 148 | 149 | fmt.Fprintf(os.Stdout, "%v \n", string(resp)) 150 | } else { 151 | for _, ref := range orphaned { 152 | fmt.Println(ref) 153 | } 154 | } 155 | 156 | return nil 157 | } 158 | -------------------------------------------------------------------------------- /api/list_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestListAllContribs(t *testing.T) { 13 | t.Log("Testing listing of all contribs") 14 | 15 | tempDir, _ := GetTempDir() 16 | 17 | testEnv := &TestEnv{currentDir: tempDir} 18 | 19 | defer testEnv.cleanup() 20 | 21 | t.Logf("Current dir '%s'", testEnv.currentDir) 22 | os.Chdir(testEnv.currentDir) 23 | 24 | _, err := CreateProject(testEnv.currentDir, "myApp", "", "") 25 | 26 | assert.Equal(t, nil, err) 27 | 28 | err = ListContribs(NewAppProject(filepath.Join(testEnv.currentDir, "myApp")), true, "all") 29 | assert.Equal(t, nil, err) 30 | 31 | } 32 | 33 | func TestListWithLegacyPkg(t *testing.T) { 34 | t.Log("Testing listing of legacy contribs") 35 | 36 | tempDir, _ := GetTempDir() 37 | 38 | testEnv := &TestEnv{currentDir: tempDir} 39 | 40 | defer testEnv.cleanup() 41 | 42 | t.Logf("Current dir '%s'", testEnv.currentDir) 43 | 44 | err := os.Chdir(tempDir) 45 | 46 | file, err := os.Create("flogo.json") 47 | if err != nil { 48 | t.Fatal(err) 49 | assert.Equal(t, true, false) 50 | } 51 | defer file.Close() 52 | fmt.Fprintf(file, newJsonString) 53 | _, err = CreateProject(testEnv.currentDir, "temp", "flogo.json", "") 54 | assert.Equal(t, nil, err) 55 | 56 | err = ListContribs(NewAppProject(filepath.Join(testEnv.currentDir, "temp")), true, "") 57 | assert.Equal(t, nil, err) 58 | } 59 | -------------------------------------------------------------------------------- /api/project.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "go/parser" 6 | "go/printer" 7 | "go/token" 8 | "os" 9 | "path/filepath" 10 | "runtime" 11 | "strings" 12 | 13 | "github.com/project-flogo/cli/common" 14 | "github.com/project-flogo/cli/util" 15 | ) 16 | 17 | const ( 18 | flogoCoreRepo = "github.com/project-flogo/core" 19 | fileFlogoJson = "flogo.json" 20 | fileEngineJson = "engine.json" 21 | fileMainGo = "main.go" 22 | fileImportsGo = "imports.go" 23 | dirSrc = "src" 24 | dirBin = "bin" 25 | ) 26 | 27 | var GOOSENV = os.Getenv("GOOS") 28 | 29 | type appProjectImpl struct { 30 | appDir string 31 | appName string 32 | srcDir string 33 | binDir string 34 | dm util.DepManager 35 | } 36 | 37 | func NewAppProject(appDir string) common.AppProject { 38 | project := &appProjectImpl{appDir: appDir} 39 | project.srcDir = filepath.Join(appDir, dirSrc) 40 | project.binDir = filepath.Join(appDir, dirBin) 41 | project.dm = util.NewDepManager(project.srcDir) 42 | project.appName = filepath.Base(appDir) 43 | return project 44 | } 45 | 46 | func (p *appProjectImpl) Validate() error { 47 | _, err := os.Stat(filepath.Join(p.appDir, fileFlogoJson)) 48 | if os.IsNotExist(err) { 49 | return fmt.Errorf("not a valid flogo app project directory, missing flogo.json") 50 | } 51 | 52 | _, err = os.Stat(p.srcDir) 53 | if os.IsNotExist(err) { 54 | return fmt.Errorf("not a valid flogo app project directory, missing 'src' diretory") 55 | } 56 | 57 | _, err = os.Stat(filepath.Join(p.srcDir, fileImportsGo)) 58 | if os.IsNotExist(err) { 59 | return fmt.Errorf("flogo app directory corrupt, missing 'src/imports.go' file") 60 | } 61 | 62 | _, err = os.Stat(filepath.Join(p.srcDir, "go.mod")) 63 | if os.IsNotExist(err) { 64 | return fmt.Errorf("flogo app directory corrupt, missing 'src/go.mod' file") 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func (p *appProjectImpl) Name() string { 71 | return p.appName 72 | } 73 | 74 | func (p *appProjectImpl) Dir() string { 75 | return p.appDir 76 | } 77 | 78 | func (p *appProjectImpl) BinDir() string { 79 | return p.binDir 80 | } 81 | 82 | func (p *appProjectImpl) SrcDir() string { 83 | return p.srcDir 84 | } 85 | 86 | func (p *appProjectImpl) DepManager() util.DepManager { 87 | return p.dm 88 | } 89 | 90 | func (p *appProjectImpl) Executable() string { 91 | 92 | var execPath string 93 | 94 | execPath = filepath.Join(p.binDir, p.appName) 95 | 96 | if GOOSENV == "windows" || (runtime.GOOS == "windows" && GOOSENV == "") { 97 | // env or cross platform is windows 98 | execPath = filepath.Join(p.binDir, p.appName+".exe") 99 | } 100 | 101 | return execPath 102 | } 103 | 104 | func (p *appProjectImpl) GetPath(flogoImport util.Import) (string, error) { 105 | return p.dm.GetPath(flogoImport) 106 | } 107 | 108 | func (p *appProjectImpl) GetGoImports(withVersion bool) ([]util.Import, error) { 109 | importsFile := filepath.Join(p.SrcDir(), fileImportsGo) 110 | 111 | fset := token.NewFileSet() 112 | file, err := parser.ParseFile(fset, importsFile, nil, parser.ImportsOnly) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | //if withVersion, parse go.mod file for version information 118 | //goModImports, err := p.dm.GetAllImports() 119 | //if err != nil { 120 | // return nil, err 121 | //} 122 | 123 | var imports []util.Import 124 | for _, is := range file.Imports { 125 | 126 | imp, err := util.ParseImport(is.Path.Value) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | imports = append(imports, imp) 132 | } 133 | 134 | return imports, nil 135 | } 136 | 137 | func (p *appProjectImpl) addImportsInGo(ignoreError bool, imports ...util.Import) error { 138 | importsFile := filepath.Join(p.SrcDir(), fileImportsGo) 139 | 140 | fset := token.NewFileSet() 141 | file, err := parser.ParseFile(fset, importsFile, nil, parser.ImportsOnly) 142 | if err != nil { 143 | return err 144 | } 145 | 146 | for _, i := range imports { 147 | err := p.DepManager().AddDependency(i) 148 | if err != nil { 149 | if ignoreError { 150 | fmt.Printf("Warning: unable to install '%s'\n", i) 151 | continue 152 | } 153 | 154 | fmt.Fprintf(os.Stderr, "Error in installing '%s'\n", i) 155 | 156 | return err 157 | } 158 | util.AddImport(fset, file, i.GoImportPath()) 159 | } 160 | 161 | f, err := os.Create(importsFile) 162 | defer f.Close() 163 | if err := printer.Fprint(f, fset, file); err != nil { 164 | return err 165 | } 166 | 167 | //p.dm.Finalize() 168 | 169 | return nil 170 | } 171 | 172 | func (p *appProjectImpl) addImportsInJson(ignoreError bool, imports ...util.Import) error { 173 | 174 | appDescriptor, err := readAppDescriptor(p) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | // list existing imports in JSON to avoid duplicates 180 | existingImports := make(map[string]util.Import) 181 | jsonImports, _ := util.ParseImports(appDescriptor.Imports) 182 | for _, e := range jsonImports { 183 | existingImports[e.GoImportPath()] = e 184 | } 185 | 186 | for _, i := range imports { 187 | val, ok := existingImports[i.GoImportPath()] 188 | if !ok { 189 | //appDescriptor.Imports = append(appDescriptor.Imports, i.CanonicalImport()) 190 | existingImports[i.GoImportPath()] = i 191 | } else { 192 | if i.CanonicalImport() != val.CanonicalImport() { 193 | delete(existingImports, val.GoImportPath()) 194 | alias := i.Alias() 195 | if val.Alias() != "" && i.Alias() == "" { 196 | alias = val.Alias() 197 | } 198 | existingImports[i.GoImportPath()] = util.NewFlogoImport(i.ModulePath(), i.RelativeImportPath(), i.Version(), alias) 199 | } 200 | } 201 | 202 | } 203 | var newImport []string 204 | for _, val := range existingImports { 205 | newImport = append(newImport, val.CanonicalImport()) 206 | } 207 | appDescriptor.Imports = newImport 208 | 209 | err = writeAppDescriptor(p, appDescriptor) 210 | if err != nil { 211 | return err 212 | } 213 | 214 | return nil 215 | } 216 | 217 | func (p *appProjectImpl) AddImports(ignoreError bool, addToJson bool, imports ...util.Import) error { 218 | err := p.addImportsInGo(ignoreError, imports...) // begin with Go imports as they are more likely to fail 219 | if err != nil { 220 | return err 221 | } 222 | 223 | if addToJson { 224 | err = p.addImportsInJson(ignoreError, imports...) // adding imports in JSON after Go imports ensure the flogo.json is self-sufficient 225 | } 226 | 227 | return err 228 | } 229 | 230 | func (p *appProjectImpl) RemoveImports(imports ...string) error { 231 | 232 | importsFile := filepath.Join(p.SrcDir(), fileImportsGo) 233 | 234 | fset := token.NewFileSet() 235 | file, err := parser.ParseFile(fset, importsFile, nil, parser.ImportsOnly) 236 | if err != nil { 237 | return err 238 | } 239 | 240 | for _, impPath := range imports { 241 | util.DeleteImport(fset, file, strings.Trim(impPath, "\"")) 242 | } 243 | 244 | f, err := os.Create(importsFile) 245 | defer f.Close() 246 | if err := printer.Fprint(f, fset, file); err != nil { 247 | return err 248 | } 249 | 250 | return nil 251 | } 252 | -------------------------------------------------------------------------------- /api/shim.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "os/exec" 8 | "path" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | 13 | "github.com/project-flogo/cli/common" 14 | "github.com/project-flogo/cli/util" 15 | ) 16 | 17 | const ( 18 | fileShimSupportGo string = "shim_support.go" 19 | fileShimGo string = "shim.go" 20 | fileBuildGo string = "build.go" 21 | fileMakefile string = "Makefile" 22 | dirShim string = "shim" 23 | ) 24 | 25 | var fileSampleShimSupport = filepath.Join("examples", "engine", "shim", fileShimSupportGo) 26 | 27 | var flogoImportPattern = regexp.MustCompile(`^(([^ ]*)[ ]+)?([^@:]*)@?([^:]*)?:?(.*)?$`) 28 | 29 | 30 | type ShimBuilder struct { 31 | appBuilder common.Builder 32 | shim string 33 | } 34 | 35 | func (sb *ShimBuilder) Build(project common.AppProject) error { 36 | 37 | err := backupMain(project) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | defer shimCleanup(project) 43 | 44 | err = createShimSupportGoFile(project) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | if Verbose() { 50 | fmt.Println("Preparing shim...") 51 | } 52 | built, err := prepareShim(project, sb.shim) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | if !built { 58 | fmt.Println("Using go build to build shim...") 59 | 60 | err := simpleGoBuild(project) 61 | if err != nil { 62 | return err 63 | } 64 | } 65 | 66 | return nil 67 | } 68 | 69 | func prepareShim(project common.AppProject, shim string) (bool, error) { 70 | 71 | buf, err := ioutil.ReadFile(filepath.Join(project.Dir(), fileFlogoJson)) 72 | if err != nil { 73 | return false, err 74 | } 75 | 76 | flogoJSON := string(buf) 77 | 78 | descriptor, err := util.ParseAppDescriptor(flogoJSON) 79 | if err != nil { 80 | return false, err 81 | } 82 | 83 | err = registerImports(project, descriptor) 84 | if err != nil { 85 | return false, err 86 | } 87 | 88 | for _, trgCfg := range descriptor.Triggers { 89 | if trgCfg.Id == shim { 90 | 91 | ref := trgCfg.Ref 92 | 93 | if trgCfg.Ref != "" { 94 | found := false 95 | ref, found = GetAliasRef("flogo:trigger", trgCfg.Ref) 96 | if !found { 97 | return false, fmt.Errorf("unable to determine ref for trigger: %s", trgCfg.Id) 98 | } 99 | } 100 | 101 | refImport, err := util.NewFlogoImportFromPath(ref) 102 | if err != nil { 103 | return false, err 104 | } 105 | 106 | impPath, err := project.GetPath(refImport) 107 | if err != nil { 108 | return false, err 109 | } 110 | 111 | var shimFilePath string 112 | 113 | shimFilePath = filepath.Join(impPath, dirShim, fileShimGo) 114 | 115 | if _, err := os.Stat(shimFilePath); err == nil { 116 | 117 | err = util.CopyFile(shimFilePath, filepath.Join(project.SrcDir(), fileShimGo)) 118 | if err != nil { 119 | return false, err 120 | } 121 | 122 | // Check if this shim based trigger has a gobuild file. If the trigger has a gobuild 123 | // execute that file, otherwise check if there is a Makefile to execute 124 | goBuildFilePath := filepath.Join(impPath, dirShim, fileBuildGo) 125 | 126 | makefilePath := filepath.Join(shimFilePath, dirShim, fileMakefile) 127 | 128 | if _, err := os.Stat(goBuildFilePath); err == nil { 129 | fmt.Println("Using build.go to build shim......") 130 | 131 | err = util.CopyFile(goBuildFilePath, filepath.Join(project.SrcDir(), fileBuildGo)) 132 | if err != nil { 133 | return false, err 134 | } 135 | 136 | // Execute go run gobuild.go 137 | err = util.ExecCmd(exec.Command("go", "run", fileBuildGo), project.SrcDir()) 138 | if err != nil { 139 | return false, err 140 | } 141 | 142 | return true, nil 143 | } else if _, err := os.Stat(makefilePath); err == nil { 144 | //look for Makefile and execute it 145 | fmt.Println("Using make file to build shim...") 146 | 147 | err = util.CopyFile(makefilePath, filepath.Join(project.SrcDir(), fileMakefile)) 148 | if err != nil { 149 | return false, err 150 | } 151 | 152 | if Verbose() { 153 | fmt.Println("Make File:", makefilePath) 154 | } 155 | 156 | // Execute make 157 | cmd := exec.Command("make", "-C", project.SrcDir()) 158 | cmd.Stdout = os.Stdout 159 | cmd.Stderr = os.Stderr 160 | cmd.Env = util.ReplaceEnvValue(os.Environ(), "GOPATH", project.Dir()) 161 | 162 | err = cmd.Run() 163 | if err != nil { 164 | return false, err 165 | } 166 | 167 | return true, nil 168 | } else { 169 | return false, nil 170 | } 171 | } 172 | 173 | break 174 | } 175 | } 176 | 177 | return false, fmt.Errorf("unable to to find shim trigger: %s", shim) 178 | } 179 | 180 | func shimCleanup(project common.AppProject) { 181 | 182 | if Verbose() { 183 | fmt.Println("Cleaning up shim support files...") 184 | } 185 | 186 | err := util.DeleteFile(filepath.Join(project.SrcDir(), fileShimSupportGo)) 187 | if err != nil { 188 | fmt.Printf("Unable to delete: %s", fileShimSupportGo) 189 | } 190 | err = util.DeleteFile(filepath.Join(project.SrcDir(), fileShimGo)) 191 | if err != nil { 192 | fmt.Printf("Unable to delete: %s", fileShimGo) 193 | } 194 | err = util.DeleteFile(filepath.Join(project.SrcDir(), fileBuildGo)) 195 | if err != nil { 196 | fmt.Printf("Unable to delete: %s", fileBuildGo) 197 | } 198 | } 199 | 200 | func createShimSupportGoFile(project common.AppProject) error { 201 | 202 | shimSrcPath := filepath.Join(project.SrcDir(), fileShimSupportGo) 203 | 204 | if Verbose() { 205 | fmt.Println("Creating shim support files...") 206 | } 207 | 208 | flogoCoreImport, err := util.NewFlogoImportFromPath(flogoCoreRepo) 209 | if err != nil { 210 | return err 211 | } 212 | 213 | corePath, err := project.GetPath(flogoCoreImport) 214 | if err != nil { 215 | return err 216 | } 217 | 218 | bytes, err := ioutil.ReadFile(filepath.Join(corePath, fileSampleShimSupport)) 219 | if err != nil { 220 | return err 221 | } 222 | 223 | err = ioutil.WriteFile(shimSrcPath, bytes, 0644) 224 | if err != nil { 225 | return err 226 | } 227 | 228 | return nil 229 | } 230 | 231 | func registerImports(project common.AppProject, appDesc *util.FlogoAppDescriptor) error { 232 | 233 | for _, anImport := range appDesc.Imports { 234 | err := registerImport(project, anImport) 235 | if err != nil { 236 | return err 237 | } 238 | } 239 | 240 | return nil 241 | } 242 | 243 | func registerImport(project common.AppProject, anImport string) error { 244 | 245 | matches := flogoImportPattern.FindStringSubmatch(anImport) 246 | 247 | parts := strings.Split(matches[3], " ") 248 | 249 | var alias string 250 | var ref string 251 | numParts := len(parts) 252 | if numParts == 1 { 253 | ref = parts[0] 254 | alias = path.Base(ref) 255 | 256 | } else if numParts == 2 { 257 | alias = parts[0] 258 | ref = parts[1] 259 | } else { 260 | return fmt.Errorf("invalid import %s", anImport) 261 | } 262 | 263 | if alias == "" || ref == "" { 264 | return fmt.Errorf("invalid import %s", anImport) 265 | } 266 | 267 | ct, err := util.GetContribType(project.DepManager(), ref) 268 | if err != nil { 269 | return err 270 | } 271 | 272 | if ct != "" { 273 | RegisterAlias(ct, alias, ref) 274 | } 275 | 276 | return nil 277 | } 278 | 279 | var aliases = make(map[string]map[string]string) 280 | 281 | func RegisterAlias(contribType string, alias, ref string) { 282 | 283 | aliasToRefMap, exists := aliases[contribType] 284 | if !exists { 285 | aliasToRefMap = make(map[string]string) 286 | aliases[contribType] = aliasToRefMap 287 | } 288 | 289 | aliasToRefMap[alias] = ref 290 | } 291 | 292 | func GetAliasRef(contribType string, alias string) (string, bool) { 293 | if alias == "" { 294 | return "", false 295 | } 296 | 297 | if alias[0] == '#' { 298 | alias = alias[1:] 299 | } 300 | aliasToRefMap, exists := aliases[contribType] 301 | if !exists { 302 | return "", false 303 | } 304 | 305 | ref, exists := aliasToRefMap[alias] 306 | if !exists { 307 | return "", false 308 | } 309 | 310 | return ref, true 311 | } 312 | -------------------------------------------------------------------------------- /api/update.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | 7 | "github.com/project-flogo/cli/common" 8 | "github.com/project-flogo/cli/util" 9 | ) 10 | 11 | func UpdatePkg(project common.AppProject, pkg string) error { 12 | 13 | if Verbose() { 14 | fmt.Printf("Updating Package: %s \n", pkg) 15 | } 16 | 17 | err := util.ExecCmd(exec.Command("go", "get", "-u", pkg), project.SrcDir()) 18 | return err 19 | } 20 | -------------------------------------------------------------------------------- /api/util.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/project-flogo/cli/common" 11 | "github.com/project-flogo/core/app" 12 | ) 13 | 14 | func readAppDescriptor(project common.AppProject) (*app.Config, error) { 15 | 16 | appDescriptorFile, err := os.Open(filepath.Join(project.Dir(), fileFlogoJson)) 17 | if err != nil { 18 | return nil, err 19 | } 20 | defer appDescriptorFile.Close() 21 | 22 | appDescriptorData, err := ioutil.ReadAll(appDescriptorFile) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | var appDescriptor app.Config 28 | err = json.Unmarshal([]byte(appDescriptorData), &appDescriptor) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return &appDescriptor, nil 34 | } 35 | 36 | func writeAppDescriptor(project common.AppProject, appDescriptor *app.Config) error { 37 | 38 | appDescriptorUpdated, err := json.MarshalIndent(appDescriptor, "", " ") 39 | if err != nil { 40 | return err 41 | } 42 | 43 | appDescriptorUpdatedJson := string(appDescriptorUpdated) 44 | 45 | err = ioutil.WriteFile(filepath.Join(project.Dir(), fileFlogoJson), []byte(appDescriptorUpdatedJson), 0644) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | return nil 51 | } 52 | 53 | func backupMain(project common.AppProject) error { 54 | mainGo := filepath.Join(project.SrcDir(), fileMainGo) 55 | mainGoBak := filepath.Join(project.SrcDir(), fileMainGo+".bak") 56 | 57 | if _, err := os.Stat(mainGo); err == nil { 58 | //main found, check for backup main in case we have to remove it 59 | if _, err := os.Stat(mainGoBak); err == nil { 60 | 61 | //remove old main backup 62 | if Verbose() { 63 | fmt.Printf("Removing old main backup: %s\n", mainGoBak) 64 | } 65 | err = os.Rename(mainGoBak, mainGo) 66 | if err != nil { 67 | return err 68 | } 69 | } 70 | if Verbose() { 71 | fmt.Println("Backing up main.go") 72 | } 73 | err = os.Rename(mainGo, mainGoBak) 74 | if err != nil { 75 | return err 76 | } 77 | } 78 | 79 | return nil 80 | } 81 | 82 | func restoreMain(project common.AppProject) error { 83 | 84 | mainGo := filepath.Join(project.SrcDir(), fileMainGo) 85 | mainGoBak := filepath.Join(project.SrcDir(), fileMainGo+".bak") 86 | 87 | if _, err := os.Stat(mainGo); err != nil { 88 | //main not found, check for backup main 89 | if _, err := os.Stat(mainGoBak); err == nil { 90 | if Verbose() { 91 | fmt.Printf("Restoring main from: %s\n", mainGoBak) 92 | } 93 | err = os.Rename(mainGoBak, mainGo) 94 | if err != nil { 95 | return err 96 | } 97 | } else if _, err := os.Stat(mainGo); err != nil { 98 | return fmt.Errorf("project corrupt, main missing") 99 | } 100 | } 101 | 102 | return nil 103 | } 104 | -------------------------------------------------------------------------------- /cmd/flogo/gen/version.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/project-flogo/cli/util" 7 | "os" 8 | ) 9 | 10 | // This Go program is aimed at being called by go:generate from "cmd/flogo/main.go" to create a "currentversion.go" file 11 | // in the "cmd/flogo" subdirectory. 12 | // 13 | // Once this file is created, it will set at runtime the version of the CLI without relying on GOPATH nor Git command to 14 | // parse the version from the source. Hence it is possible to distribute the CLI as a fully static binary. 15 | // 16 | // Users getting the CLI with a classic "go get" command will still have the version retrieved from the directory 17 | // $GOPATH/src/github.com/project-flogo/cli 18 | func main() { 19 | 20 | _, currentVersion, _ := util.GetCLIInfo() 21 | wd, err := os.Getwd() 22 | if err != nil { 23 | return 24 | } 25 | 26 | util.CreateVersionFile(wd, currentVersion) 27 | } 28 | -------------------------------------------------------------------------------- /cmd/flogo/imports.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "os" 5 | ) 6 | -------------------------------------------------------------------------------- /cmd/flogo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/project-flogo/cli/commands" 8 | "github.com/project-flogo/cli/util" 9 | ) 10 | 11 | // Not set by default, will be filled by init() function in "./currentversion.go" file, if it exists. 12 | // This latter file is generated with a "go generate" command. 13 | var Version string = "" 14 | 15 | //go:generate go run gen/version.go 16 | func main() { 17 | 18 | if util.GetGoPath() == "" { 19 | _, _ = fmt.Fprintf(os.Stderr, "Error: GOPATH must be set before running flogo cli\n") 20 | os.Exit(1) 21 | } 22 | 23 | //Initialize the commands 24 | _ = os.Setenv("GO111MODULE", "on") 25 | commands.Initialize(Version) 26 | commands.Execute() 27 | } 28 | -------------------------------------------------------------------------------- /commands/build.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | 9 | "github.com/project-flogo/cli/api" 10 | "github.com/project-flogo/cli/common" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var buildShim string 15 | var buildOptimize bool 16 | var buildEmbed bool 17 | var syncImport bool 18 | var flogoJsonFile string 19 | 20 | func init() { 21 | buildCmd.Flags().StringVarP(&buildShim, "shim", "", "", "use shim trigger") 22 | buildCmd.Flags().BoolVarP(&buildOptimize, "optimize", "o", false, "optimize build") 23 | buildCmd.Flags().BoolVarP(&buildEmbed, "embed", "e", false, "embed configuration in binary") 24 | buildCmd.Flags().StringVarP(&flogoJsonFile, "file", "f", "", "specify a flogo.json to build") 25 | buildCmd.Flags().BoolVarP(&syncImport, "sync", "s", false, "sync imports during build") 26 | rootCmd.AddCommand(buildCmd) 27 | } 28 | 29 | //Build the project. 30 | var buildCmd = &cobra.Command{ 31 | Use: "build", 32 | Short: "build the flogo application", 33 | Long: `Build the flogo application.`, 34 | PersistentPreRun: func(cmd *cobra.Command, args []string) {}, 35 | Run: func(cmd *cobra.Command, args []string) { 36 | var err error 37 | if flogoJsonFile == "" { 38 | preRun(cmd, args, verbose) 39 | options := common.BuildOptions{Shim: buildShim, OptimizeImports: buildOptimize, EmbedConfig: buildEmbed} 40 | 41 | if syncImport { 42 | err = api.SyncProjectImports(common.CurrentProject()) 43 | if err != nil { 44 | fmt.Fprintf(os.Stderr, "Error synchronzing imports: %v\n", err) 45 | os.Exit(1) 46 | } 47 | } 48 | 49 | err = api.BuildProject(common.CurrentProject(), options) 50 | if err != nil { 51 | fmt.Fprintf(os.Stderr, "Error building project: %v\n", err) 52 | os.Exit(1) 53 | } 54 | } else { 55 | //If a jsonFile is specified in the build. 56 | //Create a new project in the temp folder and copy the bin. 57 | 58 | tempDir, err := api.GetTempDir() 59 | if err != nil { 60 | fmt.Fprintf(os.Stderr, "Error getting temp dir: %v\n", err) 61 | os.Exit(1) 62 | } 63 | 64 | api.SetVerbose(verbose) 65 | tempProject, err := api.CreateProject(tempDir, "", flogoJsonFile, "latest") 66 | if err != nil { 67 | fmt.Fprintf(os.Stderr, "Error creating temp project: %v\n", err) 68 | os.Exit(1) 69 | } 70 | 71 | common.SetCurrentProject(tempProject) 72 | 73 | options := common.BuildOptions{Shim: buildShim, OptimizeImports: buildOptimize, EmbedConfig: buildEmbed} 74 | 75 | err = api.BuildProject(common.CurrentProject(), options) 76 | if err != nil { 77 | fmt.Fprintf(os.Stderr, "Error building temp project: %v\n", err) 78 | os.Exit(1) 79 | } 80 | 81 | copyBin(verbose, tempProject) 82 | } 83 | }, 84 | } 85 | 86 | func copyBin(verbose bool, tempProject common.AppProject) { 87 | 88 | currDir, err := os.Getwd() 89 | if err != nil { 90 | fmt.Fprintf(os.Stderr, "Error determining working directory: %v\n", err) 91 | os.Exit(1) 92 | } 93 | 94 | if verbose { 95 | fmt.Printf("Copying the binary from %s to %s \n", tempProject.BinDir(), currDir) 96 | } 97 | 98 | if runtime.GOOS == "windows" || api.GOOSENV == "windows" { 99 | err = os.Rename(tempProject.Executable(), filepath.Join(currDir, "main.exe")) 100 | if err != nil { 101 | fmt.Fprintf(os.Stderr, "Error renaming executable: %v\n", err) 102 | os.Exit(1) 103 | } 104 | } else { 105 | err = os.Rename(tempProject.Executable(), filepath.Join(currDir, tempProject.Name())) 106 | if err != nil { 107 | fmt.Fprintf(os.Stderr, "Error renaming executable: %v\n", err) 108 | os.Exit(1) 109 | } 110 | } 111 | 112 | if verbose { 113 | fmt.Printf("Removing the temp dir: %s\n ", tempProject.Dir()) 114 | } 115 | 116 | err = os.RemoveAll(tempProject.Dir()) 117 | if err != nil { 118 | fmt.Fprintf(os.Stderr, "Error removing temp dir: %v\n", err) 119 | os.Exit(1) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /commands/create.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/project-flogo/cli/api" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var flogoJsonPath string 12 | var coreVersion string 13 | 14 | func init() { 15 | CreateCmd.Flags().StringVarP(&flogoJsonPath, "file", "f", "", "specify a flogo.json to create project from") 16 | CreateCmd.Flags().StringVarP(&coreVersion, "cv", "", "", "specify core library version (ex. master)") 17 | rootCmd.AddCommand(CreateCmd) 18 | } 19 | 20 | var CreateCmd = &cobra.Command{ 21 | Use: "create [flags] [appName]", 22 | Short: "create a flogo application project", 23 | Long: `Creates a flogo application project.`, 24 | Args: cobra.RangeArgs(0, 1), 25 | PersistentPreRun: func(cmd *cobra.Command, args []string) {}, 26 | Run: func(cmd *cobra.Command, args []string) { 27 | 28 | api.SetVerbose(verbose) 29 | appName := "" 30 | if len(args) > 0 { 31 | appName = args[0] 32 | } 33 | 34 | currentDir, err := os.Getwd() 35 | if err != nil { 36 | fmt.Fprintf(os.Stderr, "Error determining working directory: %v\n", err) 37 | os.Exit(1) 38 | } 39 | _, err = api.CreateProject(currentDir, appName, flogoJsonPath, coreVersion) 40 | if err != nil { 41 | fmt.Fprintf(os.Stderr, "Error creating project: %v\n", err) 42 | os.Exit(1) 43 | } 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /commands/imports.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/project-flogo/cli/api" 8 | "github.com/project-flogo/cli/common" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func init() { 13 | rootCmd.AddCommand(importsCmd) 14 | importsCmd.AddCommand(importsSyncCmd) 15 | importsCmd.AddCommand(importsResolveCmd) 16 | importsCmd.AddCommand(importsListCmd) 17 | } 18 | 19 | var importsCmd = &cobra.Command{ 20 | Use: "imports", 21 | Short: "manage project imports", 22 | Long: `Manage project imports of contributions and dependencies.`, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | 25 | }, 26 | } 27 | 28 | var importsSyncCmd = &cobra.Command{ 29 | Use: "sync", 30 | Short: "sync Go imports to project imports", 31 | Long: `Synchronize Go imports to project imports.`, 32 | Run: func(cmd *cobra.Command, args []string) { 33 | 34 | err := api.SyncProjectImports(common.CurrentProject()) 35 | 36 | if err != nil { 37 | fmt.Fprintf(os.Stderr, "Error synchronzing imports: %v\n", err) 38 | os.Exit(1) 39 | } 40 | }, 41 | } 42 | 43 | var importsResolveCmd = &cobra.Command{ 44 | Use: "resolve", 45 | Short: "resolve project imports to installed version", 46 | Long: `Resolves all project imports to current installed version.`, 47 | Run: func(cmd *cobra.Command, args []string) { 48 | 49 | err := api.ResolveProjectImports(common.CurrentProject()) 50 | 51 | if err != nil { 52 | fmt.Fprintf(os.Stderr, "Error resolving import versions: %v\n", err) 53 | os.Exit(1) 54 | } 55 | }, 56 | } 57 | 58 | var importsListCmd = &cobra.Command{ 59 | Use: "list", 60 | Short: "list project imports", 61 | Long: `List all the project imports`, 62 | Run: func(cmd *cobra.Command, args []string) { 63 | 64 | err := api.ListProjectImports(common.CurrentProject()) 65 | 66 | if err != nil { 67 | fmt.Fprintf(os.Stderr, "Error listing imports: %v\n", err) 68 | os.Exit(1) 69 | } 70 | }, 71 | } 72 | -------------------------------------------------------------------------------- /commands/install.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/project-flogo/cli/api" 9 | "github.com/project-flogo/cli/common" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var replaceContrib string 14 | var contribBundleFile string 15 | 16 | func init() { 17 | installCmd.Flags().StringVarP(&replaceContrib, "replace", "r", "", "specify path to replacement contribution/dependency") 18 | installCmd.Flags().StringVarP(&contribBundleFile, "file", "f", "", "specify contribution bundle") 19 | rootCmd.AddCommand(installCmd) 20 | } 21 | 22 | var installCmd = &cobra.Command{ 23 | Use: "install [flags] ", 24 | Short: "install a flogo contribution/dependency", 25 | Long: "Installs a flogo contribution or dependency", 26 | Run: func(cmd *cobra.Command, args []string) { 27 | 28 | if contribBundleFile != "" { 29 | err := api.InstallContribBundle(common.CurrentProject(), contribBundleFile) 30 | if err != nil { 31 | fmt.Fprintf(os.Stderr, "Error installing contribution bundle: %v\n", err) 32 | os.Exit(1) 33 | } 34 | } 35 | 36 | if replaceContrib != "" { 37 | replaceContrib = strings.Replace(replaceContrib, "@", " ", -1) 38 | err := api.InstallReplacedPackage(common.CurrentProject(), replaceContrib, args[0]) 39 | if err != nil { 40 | fmt.Fprintf(os.Stderr, "Error installing contribution/dependency: %v\n", err) 41 | os.Exit(1) 42 | } 43 | } else { 44 | for _, pkg := range args { 45 | err := api.InstallPackage(common.CurrentProject(), pkg) 46 | if err != nil { 47 | fmt.Fprintf(os.Stderr, "Error installing contribution/dependency: %v\n", err) 48 | os.Exit(1) 49 | } 50 | } 51 | } 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /commands/list.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/project-flogo/cli/api" 8 | "github.com/project-flogo/cli/common" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var json bool 13 | var orphaned bool 14 | var listFilter string 15 | 16 | func init() { 17 | listCmd.Flags().BoolVarP(&json, "json", "j", true, "print in json format") 18 | listCmd.Flags().BoolVarP(&orphaned, "orphaned", "", false, "list orphaned refs") 19 | listCmd.Flags().StringVarP(&listFilter, "filter", "", "", "apply list filter [used, unused]") 20 | rootCmd.AddCommand(listCmd) 21 | } 22 | 23 | var listCmd = &cobra.Command{ 24 | Use: "list [flags]", 25 | Short: "list installed flogo contributions", 26 | Long: "List installed flogo contributions", 27 | Run: func(cmd *cobra.Command, args []string) { 28 | 29 | if orphaned { 30 | err := api.ListOrphanedRefs(common.CurrentProject(), json) 31 | if err != nil { 32 | fmt.Fprintf(os.Stderr, "Error getting orphaned refs: %v\n", err) 33 | os.Exit(1) 34 | } 35 | 36 | return 37 | } 38 | 39 | err := api.ListContribs(common.CurrentProject(), json, listFilter) 40 | if err != nil { 41 | fmt.Fprintf(os.Stderr, "Error getting list of contributions: %v\n", err) 42 | os.Exit(1) 43 | } 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /commands/plugin.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "github.com/project-flogo/cli/common" 6 | "github.com/spf13/cobra" 7 | "os" 8 | ) 9 | 10 | func init() { 11 | pluginCmd.AddCommand(pluginInstallCmd) 12 | pluginCmd.AddCommand(pluginListCmd) 13 | pluginCmd.AddCommand(pluginUpdateCmd) 14 | pluginCmd.AddCommand(pluginRemoveCmd) 15 | rootCmd.AddCommand(pluginCmd) 16 | } 17 | 18 | var pluginCmd = &cobra.Command{ 19 | Use: "plugin", 20 | Short: "manage CLI plugins", 21 | Long: "Manage CLI plugins", 22 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 23 | common.SetVerbose(verbose) 24 | }, 25 | } 26 | 27 | var pluginInstallCmd = &cobra.Command{ 28 | Use: "install ", 29 | Short: "install CLI plugin", 30 | Long: "Installs a CLI plugin", 31 | Args: cobra.ExactArgs(1), 32 | Run: func(cmd *cobra.Command, args []string) { 33 | 34 | pluginPkg := args[0] 35 | 36 | fmt.Printf("Installing plugin: %s\n", pluginPkg) 37 | 38 | err := UpdateCLI(pluginPkg, UpdateOptAdd) 39 | if err != nil { 40 | fmt.Fprintf(os.Stderr, "Error adding plugin: %v\n", err) 41 | os.Exit(1) 42 | } 43 | 44 | fmt.Printf("Installed plugin: %s\n", pluginPkg) 45 | }, 46 | } 47 | 48 | var pluginListCmd = &cobra.Command{ 49 | Use: "list", 50 | Short: "list installed plugins", 51 | Long: "Lists installed CLI plugins", 52 | Run: func(cmd *cobra.Command, args []string) { 53 | 54 | for _, pluginPkg := range common.GetPluginPkgs() { 55 | fmt.Println(pluginPkg) 56 | } 57 | }, 58 | } 59 | 60 | var pluginRemoveCmd = &cobra.Command{ 61 | Use: "remove", 62 | Short: "remove installed plugins", 63 | Long: "Remove installed CLI plugins", 64 | Args: cobra.ExactArgs(1), 65 | Run: func(cmd *cobra.Command, args []string) { 66 | 67 | pluginPkg := args[0] 68 | 69 | fmt.Printf("Removing plugin: %s\n", pluginPkg) 70 | 71 | err := UpdateCLI(pluginPkg, UpdateOptRemove) 72 | if err != nil { 73 | fmt.Fprintf(os.Stderr, "Error adding plugin: %v\n", err) 74 | os.Exit(1) 75 | } 76 | 77 | fmt.Printf("Removed plugin: %s\n", pluginPkg) 78 | }, 79 | } 80 | 81 | var pluginUpdateCmd = &cobra.Command{ 82 | Use: "update ", 83 | Short: "update plugin", 84 | Long: "Updates the specified installed CLI plugin", 85 | Args: cobra.ExactArgs(1), 86 | Run: func(cmd *cobra.Command, args []string) { 87 | 88 | pluginPkg := args[0] 89 | 90 | fmt.Printf("Updating plugin: %s\n", pluginPkg) 91 | 92 | err := UpdateCLI(pluginPkg, UpdateOptUpdate) 93 | if err != nil { 94 | fmt.Fprintf(os.Stderr, "Error updating plugin: %v\n", err) 95 | os.Exit(1) 96 | } 97 | 98 | fmt.Printf("Updated plugin: %s\n", pluginPkg) 99 | }, 100 | } 101 | -------------------------------------------------------------------------------- /commands/pluginhelper.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "go/parser" 7 | "go/printer" 8 | "go/token" 9 | "os" 10 | "os/exec" 11 | "path/filepath" 12 | "runtime" 13 | "text/template" 14 | "time" 15 | 16 | "github.com/project-flogo/cli/common" 17 | "github.com/project-flogo/cli/util" 18 | ) 19 | 20 | const ( 21 | fileImportsGo = "imports.go" 22 | 23 | UpdateOptRebuild = iota 24 | UpdateOptAdd 25 | UpdateOptRemove 26 | UpdateOptUpdate 27 | ) 28 | 29 | func UpdateCLI(pluginPkg string, updateOption int) error { 30 | 31 | exPath, err := os.Executable() 32 | if err != nil { 33 | return err 34 | } 35 | 36 | pluginSet := make(map[string]struct{}) 37 | 38 | //add installed plugins 39 | installedPlugins := common.GetPluginPkgs() 40 | for _, aPluginPkg := range installedPlugins { 41 | pluginSet[aPluginPkg] = struct{}{} 42 | fmt.Println(aPluginPkg) 43 | } 44 | 45 | if updateOption == UpdateOptAdd { 46 | // add new plugin 47 | pluginSet[pluginPkg] = struct{}{} 48 | } else if updateOption == UpdateOptRemove { 49 | delete(pluginSet, pluginPkg) 50 | } 51 | 52 | path, ver, err := util.GetCLIInfo() 53 | 54 | tmpDir := os.TempDir() 55 | basePath := filepath.Join(tmpDir, "cli") 56 | cliCmdPath := filepath.Join(basePath, "cmd", "flogo") 57 | 58 | err = os.RemoveAll(basePath) 59 | if err != nil { 60 | fmt.Println("del err:", err) 61 | } 62 | 63 | err = util.Copy(path, basePath, false) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | err = util.CreateVersionFile(cliCmdPath, ver) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | err = createPluginListFile(basePath, pluginSet) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | for plugin := range pluginSet { 79 | _, err := addPlugin(cliCmdPath, plugin) 80 | if err != nil { 81 | fmt.Println("error:", err) 82 | } 83 | } 84 | 85 | if updateOption == UpdateOptUpdate { 86 | err = util.ExecCmd(exec.Command("go", "get", "-u", pluginPkg), cliCmdPath) 87 | if err != nil { 88 | return err 89 | } 90 | } 91 | 92 | err = util.ExecCmd(exec.Command("go", "mod", "download"), basePath) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | err = util.ExecCmd(exec.Command("go", "build"), cliCmdPath) 98 | if err != nil { 99 | //fmt.Fprintf(os.Stderr, "Error: %v\n", osErr) 100 | return err 101 | } 102 | 103 | cliExe := "flogo" 104 | if runtime.GOOS == "windows" || os.Getenv("GOOS") == "windows" { 105 | cliExe = cliExe + ".exe" 106 | } 107 | 108 | err = util.Copy(filepath.Join(cliCmdPath, cliExe), exPath, false) 109 | if err != nil { 110 | //fmt.Fprintf(os.Stderr, "Error: %v\n", osErr) 111 | return err 112 | } 113 | 114 | return nil 115 | } 116 | 117 | func addPlugin(cliCmdPath, pluginPkg string) (bool, error) { 118 | 119 | err := util.ExecCmd(exec.Command("go", "get", pluginPkg), cliCmdPath) 120 | if err != nil { 121 | return false, err 122 | } 123 | 124 | added, err := addPluginImport(cliCmdPath, pluginPkg) 125 | if err != nil { 126 | return added, err 127 | } 128 | 129 | return added, nil 130 | } 131 | 132 | func addPluginImport(cliCmdPath, pkg string) (bool, error) { 133 | importsFile := filepath.Join(cliCmdPath, fileImportsGo) 134 | 135 | fset := token.NewFileSet() 136 | file, _ := parser.ParseFile(fset, importsFile, nil, parser.ImportsOnly) 137 | 138 | if file.Imports == nil { 139 | return false, errors.New("no imports found") 140 | } 141 | 142 | successful := util.AddImport(fset, file, pkg) 143 | 144 | if successful { 145 | f, err := os.Create(importsFile) 146 | if err != nil { 147 | return false, err 148 | } 149 | defer f.Close() 150 | if err := printer.Fprint(f, fset, file); err != nil { 151 | return false, err 152 | } 153 | } 154 | 155 | return successful, nil 156 | } 157 | 158 | func createPluginListFile(basePath string, plugins map[string]struct{}) error { 159 | 160 | f, err := os.Create(filepath.Join(basePath, "common", "pluginlist.go")) 161 | if err != nil { 162 | return err 163 | } 164 | defer f.Close() 165 | 166 | err = pluginListTemplate.Execute(f, struct { 167 | Timestamp time.Time 168 | PluginList map[string]struct{} 169 | }{ 170 | Timestamp: time.Now(), 171 | PluginList: plugins, 172 | }) 173 | 174 | return err 175 | } 176 | 177 | var pluginListTemplate = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT. 178 | // {{ .Timestamp }} 179 | package common 180 | 181 | func init() { 182 | 183 | {{range $k, $v := .PluginList}} 184 | pluginPkgs = append(pluginPkgs, "{{$k}}") 185 | {{end}} 186 | } 187 | `)) 188 | -------------------------------------------------------------------------------- /commands/root.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "github.com/project-flogo/cli/api" 6 | "github.com/project-flogo/cli/common" 7 | "github.com/project-flogo/cli/util" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | const ( 13 | VersionTpl = `{{with .Name}}{{printf "%s " .}}{{end}}{{printf "cli version %s" .Version}} 14 | ` 15 | ) 16 | 17 | var verbose bool 18 | 19 | //Root command 20 | var rootCmd = &cobra.Command{ 21 | Use: "flogo [flags] [command]", 22 | Short: "flogo cli", 23 | Long: `flogo command line interface for flogo applications`, 24 | 25 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 26 | preRun(cmd, args, verbose) 27 | }, 28 | } 29 | 30 | func Initialize(version string) { 31 | rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "verbose output") 32 | 33 | if len(version) > 0 { 34 | rootCmd.Version = version // use version hardcoded by a "go generate" command 35 | } else { 36 | _, rootCmd.Version, _ = util.GetCLIInfo() // guess version from sources in $GOPATH/src 37 | } 38 | 39 | rootCmd.SetVersionTemplate(VersionTpl) 40 | 41 | //Get the list of commands from the registry of commands and add. 42 | commandList := common.GetPlugins() 43 | 44 | for _, command := range commandList { 45 | 46 | rootCmd.AddCommand(command) 47 | } 48 | } 49 | 50 | func Execute() { 51 | 52 | if err := rootCmd.Execute(); err != nil { 53 | fmt.Fprintf(os.Stderr, "Error: %v\n", err) 54 | os.Exit(1) 55 | } 56 | } 57 | 58 | func preRun(cmd *cobra.Command, args []string, verbose bool) { 59 | api.SetVerbose(verbose) 60 | common.SetVerbose(verbose) 61 | 62 | builtIn := cmd.Name() == "help" || cmd.Name() == "version" 63 | 64 | if len(os.Args) > 1 && !builtIn { 65 | currentDir, err := os.Getwd() 66 | if err != nil { 67 | fmt.Fprintf(os.Stderr, "Error determining working directory: %v\n", err) 68 | os.Exit(1) 69 | } 70 | appProject := api.NewAppProject(currentDir) 71 | 72 | err = appProject.Validate() 73 | if err != nil { 74 | fmt.Fprintf(os.Stderr, "Error validating project: %v\n", err) 75 | os.Exit(1) 76 | } 77 | 78 | common.SetCurrentProject(appProject) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /commands/update.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/project-flogo/cli/api" 9 | "github.com/project-flogo/cli/common" 10 | "github.com/project-flogo/cli/util" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var updateAll bool 15 | 16 | func init() { 17 | rootCmd.AddCommand(updateCmd) 18 | updateCmd.Flags().BoolVarP(&updateAll, "all", "", false, "update all contributions") 19 | } 20 | 21 | const ( 22 | fJsonFile = "flogo.json" 23 | ) 24 | 25 | var updateCmd = &cobra.Command{ 26 | Use: "update [flags] ", 27 | Short: "update a project contribution/dependency", 28 | Long: `Updates a contribution or dependency in the project`, 29 | Run: func(cmd *cobra.Command, args []string) { 30 | 31 | updatePackage(common.CurrentProject(), args, updateAll) 32 | 33 | }, 34 | } 35 | 36 | func updatePackage(project common.AppProject, args []string, all bool) { 37 | 38 | if !all { 39 | if len(args) < 1 { 40 | fmt.Fprintf(os.Stderr, "Contribution not specified") 41 | os.Exit(1) 42 | } 43 | err := api.UpdatePkg(project, args[0]) 44 | 45 | if err != nil { 46 | fmt.Fprintf(os.Stderr, "Error updating contribution/dependency: %v\n", err) 47 | os.Exit(1) 48 | } 49 | 50 | } else { 51 | //Get all imports 52 | imports, err := util.GetAppImports(filepath.Join(project.Dir(), fJsonFile), project.DepManager(), true) 53 | if err != nil { 54 | fmt.Fprintf(os.Stderr, "Error updating all contributions: %v\n", err) 55 | os.Exit(1) 56 | } 57 | //Update each package in imports 58 | for _, imp := range imports.GetAllImports() { 59 | 60 | err = api.UpdatePkg(project, imp.GoGetImportPath()) 61 | 62 | if err != nil { 63 | fmt.Fprintf(os.Stderr, "Error updating contribution/dependency: %v\n", err) 64 | os.Exit(1) 65 | } 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /common/build.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type BuildOptions struct { 4 | OptimizeImports bool 5 | EmbedConfig bool 6 | Shim string 7 | } 8 | 9 | type Builder interface { 10 | Build(project AppProject) error 11 | } 12 | 13 | type BuildPreProcessor interface { 14 | DoPreProcessing(project AppProject, options BuildOptions) error 15 | } 16 | 17 | type BuildPostProcessor interface { 18 | DoPostProcessing(project AppProject) error 19 | } 20 | 21 | var buildPreProcessors []BuildPreProcessor 22 | var buildPostProcessors []BuildPostProcessor 23 | 24 | func RegisterBuildPreProcessor(processor BuildPreProcessor) { 25 | buildPreProcessors = append(buildPreProcessors, processor) 26 | } 27 | 28 | func BuildPreProcessors() []BuildPreProcessor { 29 | return buildPreProcessors 30 | } 31 | 32 | func RegisterBuildPostProcessor(processor BuildPostProcessor) { 33 | buildPostProcessors = append(buildPostProcessors, processor) 34 | } 35 | 36 | func BuildPostProcessors() []BuildPostProcessor { 37 | return buildPostProcessors 38 | } 39 | 40 | 41 | -------------------------------------------------------------------------------- /common/env.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/project-flogo/cli/util" 5 | ) 6 | 7 | var verbose = false 8 | var appProject AppProject 9 | 10 | func SetVerbose(enable bool) { 11 | verbose = enable 12 | util.SetVerbose(enable) 13 | } 14 | 15 | func Verbose() bool { 16 | return verbose 17 | } 18 | 19 | func CurrentProject() AppProject { 20 | return appProject 21 | } 22 | 23 | func SetCurrentProject(project AppProject) { 24 | appProject = project 25 | } 26 | -------------------------------------------------------------------------------- /common/plugin.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var commands []*cobra.Command 8 | var pluginPkgs []string 9 | 10 | func RegisterPlugin(command *cobra.Command) { 11 | commands = append(commands, command) 12 | } 13 | 14 | func GetPlugins() []*cobra.Command { 15 | 16 | tmp := make([]*cobra.Command, len(commands)) 17 | copy(tmp, commands) 18 | 19 | return tmp 20 | } 21 | 22 | func GetPluginPkgs() []string{ 23 | return pluginPkgs 24 | } 25 | -------------------------------------------------------------------------------- /common/project.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "github.com/project-flogo/cli/util" 4 | 5 | type AppProject interface { 6 | Validate() error 7 | Name() string 8 | Dir() string 9 | BinDir() string 10 | SrcDir() string 11 | Executable() string 12 | AddImports(ignoreError bool, addToJson bool, imports ...util.Import) error 13 | RemoveImports(imports ...string) error 14 | GetPath(flogoImport util.Import) (string, error) 15 | DepManager() util.DepManager 16 | 17 | GetGoImports(withVersion bool) ([]util.Import, error) 18 | } 19 | -------------------------------------------------------------------------------- /docs/commands.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Commands 8 | 9 | - [build](#build) - Build the flogo application 10 | - [create](#create) - Create a flogo application project 11 | - [help](#help) - Help about any command 12 | - [imports](#imports) - Manage project dependency imports 13 | - [install](#install) - Install a flogo contribution/dependency 14 | - [list](#list) - List installed flogo contributions 15 | - [plugin](#plugin) - Manage CLI plugins 16 | - [update](#update) - Update an application contribution/dependency 17 | 18 | ### Global Flags 19 | ``` 20 | --verbose verbose output 21 | ``` 22 | 23 | 24 | ## build 25 | 26 | This command is used to build the application. 27 | 28 | ``` 29 | Usage: 30 | flogo build [flags] 31 | 32 | Flags: 33 | -e, --embed embed configuration in binary 34 | -f, --file string specify a flogo.json to build 35 | -o, --optimize optimize build 36 | --shim string use shim trigger 37 | ``` 38 | _**Note:** the optimize flag removes unused trigger, acitons and activites from the built binary._ 39 | 40 | 41 | ### Examples 42 | Build the current project application 43 | 44 | ```bash 45 | $ flogo build 46 | ``` 47 | Build an application directly from a flogo.json 48 | 49 | ```bash 50 | $ flogo build -f flogo.json 51 | ``` 52 | _**Note:** this command will only generate the application binary for the specified json and can be run outside of a flogo application project_ 53 | 54 | ## create 55 | 56 | This command is used to create a flogo application project. 57 | 58 | ``` 59 | Usage: 60 | flogo create [flags] [appName] 61 | 62 | Flags: 63 | --cv string specify core library version (ex. master) 64 | -f, --file string specify a flogo.json to create project from 65 | ``` 66 | 67 | _**Note:** when using the --cv flag to specify a version, the exact version specified might not be used the project. The application will install the version that satisfies all the dependency constraints. Typically this flag is used when trying to use the master version of the core library._ 68 | 69 | ### Examples 70 | 71 | Create a base sample project with a specific name: 72 | 73 | ``` 74 | $ flogo create my_app 75 | ``` 76 | 77 | Create a project from an existing flogo application descriptor: 78 | 79 | ``` 80 | $ flogo create -f myapp.json 81 | ``` 82 | 83 | ## help 84 | 85 | This command shows help for any flogo commands. 86 | 87 | ``` 88 | Usage: 89 | flogo help [command] 90 | ``` 91 | 92 | ### Examples 93 | Get help for the build command: 94 | 95 | ```bash 96 | $ flogo help build 97 | ``` 98 | ## imports 99 | 100 | This command helps manage project imports of contributions and dependencies. 101 | 102 | ``` 103 | Usage: 104 | flogo imports [command] 105 | 106 | Available Commands: 107 | sync sync Go imports to project imports 108 | resolve resolve project imports to installed version 109 | list list project imports 110 | ``` 111 | 112 | ## install 113 | 114 | This command is used to install a flogo contribution or dependency. 115 | 116 | ``` 117 | Usage: 118 | flogo install [flags] 119 | 120 | Flags: 121 | -f, --file string specify contribution bundle 122 | -r, --replace string specify path to replacement contribution/dependency 123 | ``` 124 | 125 | ### Examples 126 | Install the basic REST trigger: 127 | 128 | ```bash 129 | $ flogo install github.com/project-flogo/contrib/trigger/rest 130 | ``` 131 | Install a contribution that you are currently developing on your computer: 132 | 133 | ```bash 134 | $ flogo install -r /tmp/dev/myactivity github.com/myuser/myactivity 135 | ``` 136 | 137 | Install a contribution that is being developed by different person on their fork: 138 | 139 | ```bash 140 | $ flogo install -r github.com/otherusr/myactivity@master github.com/myuser/myactivity 141 | ``` 142 | 143 | ## list 144 | 145 | This command lists installed contributions in your application 146 | 147 | ``` 148 | Usage: 149 | flogo list [flags] 150 | 151 | Flags: 152 | --filter string apply list filter [used, unused] 153 | -j, --json print in json format (default true) 154 | --orphaned list orphaned refs 155 | ``` 156 | _**Note** orphaned refs are `ref` entries that use an import alias (ex. `"ref": "#log"`) which has no corresponding import._ 157 | 158 | ### Examples 159 | List all installed contributions: 160 | 161 | ```bash 162 | $ flogo list 163 | ``` 164 | List all contributions directly used by the application: 165 | 166 | ```bash 167 | $ flogo list --filter used 168 | ``` 169 | _**Note:** the results of this command are the only contributions that will be compiled into your application when using `flogo build` with the optimize flag_ 170 | 171 | 172 | ## plugin 173 | 174 | This command is used to install a plugin to the Flogo CLI. 175 | 176 | ``` 177 | Usage: 178 | flogo plugin [command] 179 | 180 | Available Commands: 181 | install install CLI plugin 182 | list list installed plugins 183 | update update plugin 184 | ``` 185 | 186 | ### Examples 187 | List all installed plugins: 188 | 189 | ```bash 190 | $ flogo plugin list 191 | ``` 192 | Install the legacy support plugin: 193 | 194 | ```bash 195 | $ flogo plugin install github.com/project-flogo/legacybridge/cli` 196 | ``` 197 | _**Note:** more information on the legacy support plugin can be found [here](https://github.com/project-flogo/legacybridge/tree/master/cli)_ 198 | 199 | Install and use custom plugin: 200 | 201 | ``` 202 | $ flogo plugin install github.com/myuser/myplugin 203 | 204 | $ flogo `your_command` 205 | ``` 206 |
207 | More information on Flogo CLI plugins can be found [here](plugins.md) 208 | 209 | ## update 210 | 211 | This command updates a contribution or dependency in the project. 212 | 213 | ``` 214 | Usage: 215 | flogo update [flags] 216 | 217 | ``` 218 | ### Examples 219 | Update you log activity to master: 220 | 221 | ```bash 222 | $ flogo update github.com/project-flogo/contrib/activity/log@master 223 | ``` 224 | 225 | Update your flogo core library to latest master: 226 | 227 | ```bash 228 | $ flogo update github.com/project-flogo/core@master 229 | ``` 230 | -------------------------------------------------------------------------------- /docs/plugins.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Plugins 8 | 9 | The Flogo CLI has support for plugins. These plugins can be used to extend the Flogo CLI command. 10 | 11 | ## Creating a CLI plugin 12 | 13 | First lets setup the go project: 14 | 15 | ```bash 16 | # Create a directory for your plugin project 17 | $ mkdir myplugin 18 | 19 | # Go to the directory 20 | $ cd myplugin 21 | 22 | # Initialize the Go module information 23 | $ go mod init github.com/myuser/myplugin 24 | 25 | # Edit/Create the plugin code 26 | $ vi myplugin.go 27 | ``` 28 | 29 | Next lets create the code for our simple plugin: 30 | 31 | ```go 32 | package myplugin 33 | 34 | import ( 35 | "fmt" 36 | "github.com/project-flogo/cli/common" // Flogo CLI support code 37 | "github.com/spf13/cobra" 38 | ) 39 | 40 | func init() { 41 | common.RegisterPlugin(myCmd) 42 | } 43 | 44 | var myCmd = &cobra.Command{ 45 | Use: "mycmd", 46 | Short: "says hello world", 47 | Long: "This plugin command says hello world", 48 | Run: func(cmd *cobra.Command, args []string) { 49 | fmt.Println("Hello World") 50 | }, 51 | } 52 | ``` 53 | Once you save the code, we need to fix up the Go Module dependencies. 54 | 55 | ```bash 56 | $ go mod tidy 57 | ``` 58 | 59 | Now you are ready to test out your plugin. First you must host your plugin in your git repository. Then you are ready to install and run your plugin 60 | 61 | ``` 62 | # Install your plugin 63 | $ flogo plugin install github.com/myuser/myplugin 64 | 65 | # Run your new plugin command 66 | $ flogo mycmd 67 | ``` 68 | -------------------------------------------------------------------------------- /examples/simple-plugin/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/myuser/myplugin 2 | 3 | require ( 4 | github.com/project-flogo/cli v0.9.0 5 | github.com/spf13/cobra v0.0.5 6 | ) 7 | 8 | go 1.12 9 | -------------------------------------------------------------------------------- /examples/simple-plugin/myplugin.go: -------------------------------------------------------------------------------- 1 | package myplugin 2 | 3 | import ( 4 | "fmt" 5 | "github.com/project-flogo/cli/common" // Flogo CLI support code 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func init() { 10 | common.RegisterPlugin(myCmd) 11 | } 12 | 13 | var myCmd = &cobra.Command{ 14 | Use: "mycmd", 15 | Short: "says hello world", 16 | Long: `This plugin command says hello world`, 17 | Run: func(cmd *cobra.Command, args []string) { 18 | fmt.Println("Hello World") 19 | }, 20 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/project-flogo/cli 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/coreos/go-semver v0.3.1 7 | github.com/msoap/byline v1.1.1 8 | github.com/project-flogo/core v1.6.5 9 | github.com/spf13/cobra v1.7.0 10 | github.com/stretchr/testify v1.8.0 11 | ) 12 | 13 | require ( 14 | github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect 15 | github.com/cpuguy83/go-md2man v1.0.10 // indirect 16 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 17 | github.com/mitchellh/go-homedir v1.1.0 // indirect 18 | github.com/spf13/pflag v1.0.5 // indirect 19 | github.com/spf13/viper v1.3.2 // indirect 20 | go.uber.org/atomic v1.11.0 // indirect 21 | go.uber.org/multierr v1.11.0 // indirect 22 | go.uber.org/zap v1.24.0 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= 3 | github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= 4 | github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= 5 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 6 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 7 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 8 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 9 | github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= 10 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 11 | github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= 12 | github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= 13 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 14 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 18 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 19 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 20 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 21 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 22 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 23 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 24 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 25 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 26 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 27 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 28 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 29 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 30 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 31 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 32 | github.com/msoap/byline v1.1.1 h1:imxWvm9wIHNGePF/peiOxcL1vgVLK3/qKsMW75XZn9c= 33 | github.com/msoap/byline v1.1.1/go.mod h1:E2oCrXddpzrmu4NmrwEv4Qiyweo62Yp3+w3IN3X2sq8= 34 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 35 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 36 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 37 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 38 | github.com/project-flogo/core v0.9.5-beta.1 h1:q+LHD1dJsN/e6fqSmgJ0CZ2H8P+zGw3B7gkbE3drM/g= 39 | github.com/project-flogo/core v0.9.5-beta.1/go.mod h1:QGWi7TDLlhGUaYH3n/16ImCuulbEHGADYEXyrcHhX7U= 40 | github.com/project-flogo/core v1.6.5 h1:YR0FYwdbf24e6UM7AAcFIyxpDO7jlhs+q5JTuJTAY6A= 41 | github.com/project-flogo/core v1.6.5/go.mod h1:fapTXUhLxDeAHyb6eMkuwnYswO8FpZJAMat055QVdJE= 42 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 43 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 44 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 45 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 46 | github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= 47 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 48 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 49 | github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= 50 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 51 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 52 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 53 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 54 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 55 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 56 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 57 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 58 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 59 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 60 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 61 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 62 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 63 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 64 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 65 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 66 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 67 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 68 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 69 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 70 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 71 | github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= 72 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 73 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 74 | go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= 75 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 76 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 77 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 78 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 79 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 80 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 81 | go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 82 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 83 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 84 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 85 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 86 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 87 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 88 | go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= 89 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 90 | go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= 91 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= 92 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= 93 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 94 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 95 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 96 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 97 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 98 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 99 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 100 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 101 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 102 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 103 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 104 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 105 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 106 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 107 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 108 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 109 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 110 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 111 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 112 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 113 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 114 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 115 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 116 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 117 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 118 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 119 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 120 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 121 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 122 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 123 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 124 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 125 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 126 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 127 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 128 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 129 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 130 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 131 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 132 | -------------------------------------------------------------------------------- /test/flogo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_APP_NAME_", 3 | "type": "flogo:app", 4 | "version": "0.0.1", 5 | "description": "My flogo application description", 6 | "appModel": "1.1.0", 7 | "imports": [ 8 | "github.com/project-flogo/contrib/trigger/rest", 9 | "github.com/project-flogo/flow", 10 | "github.com/project-flogo/contrib/activity/log" 11 | ], 12 | "triggers": [ 13 | { 14 | "id": "my_rest_trigger", 15 | "ref": "#rest", 16 | "settings": { 17 | "port": "8888" 18 | }, 19 | "handlers": [ 20 | { 21 | "settings": { 22 | "method": "GET", 23 | "path": "/test/:val" 24 | }, 25 | "action": { 26 | "ref": "#flow", 27 | "settings": { 28 | "flowURI": "res://flow:simple_flow" 29 | }, 30 | "input": { 31 | "in": "$.pathParams.val" 32 | } 33 | } 34 | } 35 | ] 36 | } 37 | ], 38 | "resources": [ 39 | { 40 | "id": "flow:simple_flow", 41 | "data": { 42 | "name": "simple_flow", 43 | "metadata": { 44 | "input": [ 45 | { "name": "in", "type": "string", "value": "test" } 46 | ], 47 | "output": [ 48 | { "name": "out", "type": "string" } 49 | ] 50 | }, 51 | "tasks": [ 52 | { 53 | "id": "log", 54 | "name": "Log Message", 55 | "activity": { 56 | "ref": "#log", 57 | "input": { 58 | "message": "$flow.in", 59 | "addDetails": false 60 | } 61 | } 62 | } 63 | ] 64 | } 65 | } 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /util/app.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | // PartialAppDescriptor is the descriptor for a Flogo application 12 | type PartialAppDescriptor struct { 13 | AppModel string `json:"appModel"` 14 | Imports []string `json:"imports"` 15 | Triggers []interface{} `json:"triggers"` 16 | Resources []interface{} `json:"resources"` 17 | Actions []interface{} `json:"actions"` 18 | } 19 | 20 | type void struct{} 21 | 22 | type AppImportDetails struct { 23 | Imp Import 24 | ContribDesc *FlogoContribDescriptor 25 | TopLevel bool // a toplevel import i.e. from imports section 26 | HasAliasRef bool // imports alias is used by a contrib reference 27 | HasDirectRef bool // a direct reference exists for this import 28 | } 29 | 30 | func (d *AppImportDetails) Referenced() bool { 31 | return d.HasAliasRef || d.HasDirectRef 32 | } 33 | 34 | func (d *AppImportDetails) IsCoreContrib() bool { 35 | 36 | if d.ContribDesc == nil { 37 | return false 38 | } 39 | ct := d.ContribDesc.GetContribType() 40 | 41 | switch ct { 42 | case "action", "trigger", "activity": 43 | return true 44 | default: 45 | return false 46 | } 47 | } 48 | 49 | type AppImports struct { 50 | imports map[string]*AppImportDetails 51 | orphanedRef map[string]void 52 | 53 | resolveContribs bool 54 | depManager DepManager 55 | } 56 | 57 | func (ai *AppImports) addImports(imports []string) error { 58 | for _, anImport := range imports { 59 | flogoImport, err := ParseImport(anImport) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | if _, exists := ai.imports[flogoImport.GoImportPath()]; exists { 65 | //todo warn about duplicate import? 66 | continue 67 | } 68 | 69 | details, err := ai.newImportDetails(flogoImport) 70 | if err != nil { 71 | return err 72 | } 73 | details.TopLevel = true 74 | 75 | ai.imports[flogoImport.GoImportPath()] = details 76 | } 77 | 78 | return nil 79 | } 80 | 81 | func (ai *AppImports) addReference(ref string, contribType string) error { 82 | cleanedRef := strings.TrimSpace(ref) 83 | 84 | if cleanedRef[0] == '#' { 85 | if !ai.resolveContribs { 86 | // wont be able to determine contribTypes for existing imports, so just return 87 | return nil 88 | } 89 | 90 | alias := cleanedRef[1:] 91 | found := false 92 | for _, importDetails := range ai.imports { 93 | 94 | if importDetails.TopLevel { 95 | // alias refs can only be to toplevel imports 96 | if importDetails.Imp.CanonicalAlias() == alias && importDetails.ContribDesc != nil && 97 | importDetails.ContribDesc.GetContribType() == contribType { 98 | importDetails.HasAliasRef = true 99 | found = true 100 | break 101 | } 102 | } 103 | } 104 | 105 | if !found { 106 | ai.orphanedRef[cleanedRef] = void{} 107 | } 108 | 109 | } else { 110 | flogoImport, err := ParseImport(ref) 111 | if err != nil { 112 | return err 113 | } 114 | 115 | if imp, exists := ai.imports[flogoImport.GoImportPath()]; exists { 116 | if !imp.TopLevel { 117 | //already accounted for 118 | return nil 119 | } 120 | 121 | imp.HasDirectRef = true 122 | return nil 123 | } 124 | 125 | //doesn't exists so add new import 126 | details, err := ai.newImportDetails(flogoImport) 127 | if err != nil { 128 | return err 129 | } 130 | 131 | ai.imports[flogoImport.GoImportPath()] = details 132 | } 133 | 134 | return nil 135 | } 136 | 137 | func (ai *AppImports) newImportDetails(anImport Import) (*AppImportDetails, error) { 138 | details := &AppImportDetails{Imp: anImport} 139 | 140 | if ai.resolveContribs { 141 | desc, err := GetContribDescriptorFromImport(ai.depManager, anImport) 142 | if err != nil { 143 | return nil, err 144 | } 145 | details.ContribDesc = desc 146 | } 147 | 148 | return details, nil 149 | } 150 | 151 | func (ai *AppImports) GetOrphanedReferences() []string { 152 | var refs []string 153 | for ref := range ai.orphanedRef { 154 | refs = append(refs, ref) 155 | } 156 | 157 | return refs 158 | } 159 | 160 | func (ai *AppImports) GetAllImports() []Import { 161 | var allImports []Import 162 | for _, details := range ai.imports { 163 | allImports = append(allImports, details.Imp) 164 | } 165 | 166 | return allImports 167 | } 168 | 169 | func (ai *AppImports) GetAllImportDetails() []*AppImportDetails { 170 | 171 | var allImports []*AppImportDetails 172 | for _, details := range ai.imports { 173 | allImports = append(allImports, details) 174 | } 175 | 176 | return allImports 177 | } 178 | 179 | func GetAppImports(appJsonFile string, depManager DepManager, resolveContribs bool) (*AppImports, error) { 180 | appJson, err := os.Open(appJsonFile) 181 | if err != nil { 182 | return nil, err 183 | } 184 | 185 | bytes, err := ioutil.ReadAll(appJson) 186 | if err != nil { 187 | return nil, err 188 | } 189 | 190 | appDesc := &PartialAppDescriptor{} 191 | err = json.Unmarshal(bytes, appDesc) 192 | if err != nil { 193 | fmt.Fprintf(os.Stderr, "Unable to unmarshal flogo app json: %s", appJsonFile) 194 | return nil, err 195 | } 196 | 197 | ai := &AppImports{depManager: depManager, resolveContribs: resolveContribs} 198 | ai.imports = make(map[string]*AppImportDetails) 199 | ai.orphanedRef = make(map[string]void) 200 | 201 | err = ai.addImports(appDesc.Imports) 202 | if err != nil { 203 | return nil, err 204 | } 205 | 206 | err = extractAppReferences(ai, appDesc) 207 | 208 | return ai, err 209 | } 210 | 211 | func extractAppReferences(ai *AppImports, appDesc *PartialAppDescriptor) error { 212 | 213 | //triggers 214 | for _, trg := range appDesc.Triggers { 215 | if trgMap, ok := trg.(map[string]interface{}); ok { 216 | 217 | // a ref should exists for every trigger 218 | if refVal, ok := trgMap["ref"]; ok { 219 | if strVal, ok := refVal.(string); ok { 220 | err := ai.addReference(strVal, "trigger") 221 | if err != nil { 222 | return err 223 | } 224 | } 225 | } 226 | 227 | // actions are under handlers, so assume an action contribType 228 | err := extractReferences(ai, trgMap["handlers"], "action") 229 | if err != nil { 230 | return err 231 | } 232 | } 233 | } 234 | 235 | //in actions section, refs should be to actions 236 | err := extractReferences(ai, appDesc.Actions, "action") //action 237 | if err != nil { 238 | return err 239 | } 240 | 241 | //in resources section, refs should be to activities 242 | err = extractReferences(ai, appDesc.Resources, "activity") //activity 243 | if err != nil { 244 | return err 245 | } 246 | 247 | return nil 248 | } 249 | 250 | func extractReferences(ai *AppImports, item interface{}, contribType string) error { 251 | switch t := item.(type) { 252 | case map[string]interface{}: 253 | for key, val := range t { 254 | if strVal, ok := val.(string); ok { 255 | if key == "ref" { 256 | err := ai.addReference(strVal, contribType) 257 | if err != nil { 258 | return err 259 | } 260 | } 261 | } else { 262 | err := extractReferences(ai, val, contribType) 263 | if err != nil { 264 | return err 265 | } 266 | } 267 | } 268 | case []interface{}: 269 | for _, val := range t { 270 | err := extractReferences(ai, val, contribType) 271 | if err != nil { 272 | return err 273 | } 274 | } 275 | default: 276 | } 277 | 278 | return nil 279 | } 280 | -------------------------------------------------------------------------------- /util/ast.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/token" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // Copyright 2013 The Go Authors. All rights reserved. 12 | // Use of this source code is governed by a BSD-style 13 | // license that can be found in the LICENSE file. 14 | 15 | // code from: https://github.com/golang/tools/blob/master/go/ast/astutil/imports.go 16 | 17 | // AddImport adds the import path to the file f, if absent. 18 | func AddImport(fset *token.FileSet, f *ast.File, ipath string) (added bool) { 19 | return addNamedImport(fset, f, "_", ipath) 20 | } 21 | 22 | // AddNamedImport adds the import path to the file f, if absent. 23 | // If name is not empty, it is used to rename the import. 24 | // 25 | // For example, calling 26 | // AddNamedImport(fset, f, "pathpkg", "path") 27 | // adds 28 | // import pathpkg "path" 29 | func addNamedImport(fset *token.FileSet, f *ast.File, name, ipath string) (added bool) { 30 | if imports(f, ipath) { 31 | return false 32 | } 33 | 34 | newImport := &ast.ImportSpec{ 35 | Path: &ast.BasicLit{ 36 | Kind: token.STRING, 37 | Value: strconv.Quote(ipath), 38 | }, 39 | } 40 | if name != "" { 41 | newImport.Name = &ast.Ident{Name: name} 42 | } 43 | 44 | // Find an import decl to add to. 45 | // The goal is to find an existing import 46 | // whose import path has the longest shared 47 | // prefix with ipath. 48 | var ( 49 | bestMatch = -1 // length of longest shared prefix 50 | lastImport = -1 // index in f.Decls of the file's final import decl 51 | impDecl *ast.GenDecl // import decl containing the best match 52 | impIndex = -1 // spec index in impDecl containing the best match 53 | 54 | isThirdPartyPath = isThirdParty(ipath) 55 | ) 56 | for i, decl := range f.Decls { 57 | gen, ok := decl.(*ast.GenDecl) 58 | if ok && gen.Tok == token.IMPORT { 59 | lastImport = i 60 | // Do not add to import "C", to avoid disrupting the 61 | // association with its doc comment, breaking cgo. 62 | if declImports(gen, "C") { 63 | continue 64 | } 65 | 66 | // Match an empty import decl if that's all that is available. 67 | if len(gen.Specs) == 0 && bestMatch == -1 { 68 | impDecl = gen 69 | } 70 | 71 | // Compute longest shared prefix with imports in this group and find best 72 | // matched import spec. 73 | // 1. Always prefer import spec with longest shared prefix. 74 | // 2. While match length is 0, 75 | // - for stdlib package: prefer first import spec. 76 | // - for third party package: prefer first third party import spec. 77 | // We cannot use last import spec as best match for third party package 78 | // because grouped imports are usually placed last by goimports -local 79 | // flag. 80 | // See issue #19190. 81 | seenAnyThirdParty := false 82 | for j, spec := range gen.Specs { 83 | impspec := spec.(*ast.ImportSpec) 84 | p := importPath(impspec) 85 | n := matchLen(p, ipath) 86 | if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) { 87 | bestMatch = n 88 | impDecl = gen 89 | impIndex = j 90 | } 91 | seenAnyThirdParty = seenAnyThirdParty || isThirdParty(p) 92 | } 93 | } 94 | } 95 | 96 | // If no import decl found, add one after the last import. 97 | if impDecl == nil { 98 | impDecl = &ast.GenDecl{ 99 | Tok: token.IMPORT, 100 | } 101 | if lastImport >= 0 { 102 | impDecl.TokPos = f.Decls[lastImport].End() 103 | } else { 104 | // There are no existing imports. 105 | // Our new import, preceded by a blank line, goes after the package declaration 106 | // and after the comment, if any, that starts on the same line as the 107 | // package declaration. 108 | impDecl.TokPos = f.Package 109 | 110 | file := fset.File(f.Package) 111 | pkgLine := file.Line(f.Package) 112 | for _, c := range f.Comments { 113 | if file.Line(c.Pos()) > pkgLine { 114 | break 115 | } 116 | // +2 for a blank line 117 | impDecl.TokPos = c.End() + 2 118 | } 119 | } 120 | f.Decls = append(f.Decls, nil) 121 | copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:]) 122 | f.Decls[lastImport+1] = impDecl 123 | } 124 | 125 | // Insert new import at insertAt. 126 | insertAt := 0 127 | if impIndex >= 0 { 128 | // insert after the found import 129 | insertAt = impIndex + 1 130 | } 131 | impDecl.Specs = append(impDecl.Specs, nil) 132 | copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:]) 133 | impDecl.Specs[insertAt] = newImport 134 | pos := impDecl.Pos() 135 | if insertAt > 0 { 136 | // If there is a comment after an existing import, preserve the comment 137 | // position by adding the new import after the comment. 138 | if spec, ok := impDecl.Specs[insertAt-1].(*ast.ImportSpec); ok && spec.Comment != nil { 139 | pos = spec.Comment.End() 140 | } else { 141 | // Assign same position as the previous import, 142 | // so that the sorter sees it as being in the same block. 143 | pos = impDecl.Specs[insertAt-1].Pos() 144 | } 145 | } 146 | if newImport.Name != nil { 147 | newImport.Name.NamePos = pos 148 | } 149 | newImport.Path.ValuePos = pos 150 | newImport.EndPos = pos 151 | 152 | // Clean up parens. impDecl contains at least one spec. 153 | if len(impDecl.Specs) == 1 { 154 | // Remove unneeded parens. 155 | impDecl.Lparen = token.NoPos 156 | } else if !impDecl.Lparen.IsValid() { 157 | // impDecl needs parens added. 158 | impDecl.Lparen = impDecl.Specs[0].Pos() 159 | } 160 | 161 | f.Imports = append(f.Imports, newImport) 162 | 163 | if len(f.Decls) <= 1 { 164 | return true 165 | } 166 | 167 | // Merge all the import declarations into the first one. 168 | var first *ast.GenDecl 169 | for i := 0; i < len(f.Decls); i++ { 170 | decl := f.Decls[i] 171 | gen, ok := decl.(*ast.GenDecl) 172 | if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") { 173 | continue 174 | } 175 | if first == nil { 176 | first = gen 177 | continue // Don't touch the first one. 178 | } 179 | // We now know there is more than one package in this import 180 | // declaration. Ensure that it ends up parenthesized. 181 | first.Lparen = first.Pos() 182 | // Move the imports of the other import declaration to the first one. 183 | for _, spec := range gen.Specs { 184 | spec.(*ast.ImportSpec).Path.ValuePos = first.Pos() 185 | first.Specs = append(first.Specs, spec) 186 | } 187 | f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) 188 | i-- 189 | } 190 | 191 | return true 192 | } 193 | 194 | func isThirdParty(importPath string) bool { 195 | // Third party package import path usually contains "." (".com", ".org", ...) 196 | // This logic is taken from golang.org/x/tools/imports package. 197 | return strings.Contains(importPath, ".") 198 | } 199 | 200 | // DeleteImport deletes the import path from the file f, if present. 201 | func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) { 202 | return deleteNamedImport(fset, f, "_", path) 203 | } 204 | 205 | // DeleteNamedImport deletes the import with the given name and path from the file f, if present. 206 | func deleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) { 207 | var delspecs []*ast.ImportSpec 208 | var delcomments []*ast.CommentGroup 209 | 210 | // Find the import nodes that import path, if any. 211 | for i := 0; i < len(f.Decls); i++ { 212 | decl := f.Decls[i] 213 | gen, ok := decl.(*ast.GenDecl) 214 | if !ok || gen.Tok != token.IMPORT { 215 | continue 216 | } 217 | for j := 0; j < len(gen.Specs); j++ { 218 | spec := gen.Specs[j] 219 | impspec := spec.(*ast.ImportSpec) 220 | if impspec.Name == nil && name != "" { 221 | continue 222 | } 223 | if impspec.Name != nil && impspec.Name.Name != name { 224 | continue 225 | } 226 | if importPath(impspec) != path { 227 | continue 228 | } 229 | 230 | // We found an import spec that imports path. 231 | // Delete it. 232 | delspecs = append(delspecs, impspec) 233 | deleted = true 234 | copy(gen.Specs[j:], gen.Specs[j+1:]) 235 | gen.Specs = gen.Specs[:len(gen.Specs)-1] 236 | 237 | // If this was the last import spec in this decl, 238 | // delete the decl, too. 239 | if len(gen.Specs) == 0 { 240 | copy(f.Decls[i:], f.Decls[i+1:]) 241 | f.Decls = f.Decls[:len(f.Decls)-1] 242 | i-- 243 | break 244 | } else if len(gen.Specs) == 1 { 245 | if impspec.Doc != nil { 246 | delcomments = append(delcomments, impspec.Doc) 247 | } 248 | if impspec.Comment != nil { 249 | delcomments = append(delcomments, impspec.Comment) 250 | } 251 | for _, cg := range f.Comments { 252 | // Found comment on the same line as the import spec. 253 | if cg.End() < impspec.Pos() && fset.Position(cg.End()).Line == fset.Position(impspec.Pos()).Line { 254 | delcomments = append(delcomments, cg) 255 | break 256 | } 257 | } 258 | 259 | spec := gen.Specs[0].(*ast.ImportSpec) 260 | 261 | // Move the documentation right after the import decl. 262 | if spec.Doc != nil { 263 | for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Doc.Pos()).Line { 264 | fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line) 265 | } 266 | } 267 | for _, cg := range f.Comments { 268 | if cg.End() < spec.Pos() && fset.Position(cg.End()).Line == fset.Position(spec.Pos()).Line { 269 | for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Pos()).Line { 270 | fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line) 271 | } 272 | break 273 | } 274 | } 275 | } 276 | if j > 0 { 277 | lastImpspec := gen.Specs[j-1].(*ast.ImportSpec) 278 | lastLine := fset.Position(lastImpspec.Path.ValuePos).Line 279 | line := fset.Position(impspec.Path.ValuePos).Line 280 | 281 | // We deleted an entry but now there may be 282 | // a blank line-sized hole where the import was. 283 | if line-lastLine > 1 { 284 | // There was a blank line immediately preceding the deleted import, 285 | // so there's no need to close the hole. 286 | // Do nothing. 287 | } else if line != fset.File(gen.Rparen).LineCount() { 288 | // There was no blank line. Close the hole. 289 | fset.File(gen.Rparen).MergeLine(line) 290 | } 291 | } 292 | j-- 293 | } 294 | } 295 | 296 | // Delete imports from f.Imports. 297 | for i := 0; i < len(f.Imports); i++ { 298 | imp := f.Imports[i] 299 | for j, del := range delspecs { 300 | if imp == del { 301 | copy(f.Imports[i:], f.Imports[i+1:]) 302 | f.Imports = f.Imports[:len(f.Imports)-1] 303 | copy(delspecs[j:], delspecs[j+1:]) 304 | delspecs = delspecs[:len(delspecs)-1] 305 | i-- 306 | break 307 | } 308 | } 309 | } 310 | 311 | // Delete comments from f.Comments. 312 | for i := 0; i < len(f.Comments); i++ { 313 | cg := f.Comments[i] 314 | for j, del := range delcomments { 315 | if cg == del { 316 | copy(f.Comments[i:], f.Comments[i+1:]) 317 | f.Comments = f.Comments[:len(f.Comments)-1] 318 | copy(delcomments[j:], delcomments[j+1:]) 319 | delcomments = delcomments[:len(delcomments)-1] 320 | i-- 321 | break 322 | } 323 | } 324 | } 325 | 326 | if len(delspecs) > 0 { 327 | panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs)) 328 | } 329 | 330 | return 331 | } 332 | 333 | // 334 | //// RewriteImport rewrites any import of path oldPath to path newPath. 335 | //func RewriteImport(fset *token.FileSet, f *ast.File, oldPath, newPath string) (rewrote bool) { 336 | // for _, imp := range f.Imports { 337 | // if importPath(imp) == oldPath { 338 | // rewrote = true 339 | // // record old End, because the default is to compute 340 | // // it using the length of imp.Path.Value. 341 | // imp.EndPos = imp.End() 342 | // imp.Path.Value = strconv.Quote(newPath) 343 | // } 344 | // } 345 | // return 346 | //} 347 | // 348 | //// UsesImport reports whether a given import is used. 349 | //func UsesImport(f *ast.File, path string) (used bool) { 350 | // spec := importSpec(f, path) 351 | // if spec == nil { 352 | // return 353 | // } 354 | // 355 | // name := spec.Name.String() 356 | // switch name { 357 | // case "": 358 | // // If the package name is not explicitly specified, 359 | // // make an educated guess. This is not guaranteed to be correct. 360 | // lastSlash := strings.LastIndex(path, "/") 361 | // if lastSlash == -1 { 362 | // name = path 363 | // } else { 364 | // name = path[lastSlash+1:] 365 | // } 366 | // case "_", ".": 367 | // // Not sure if this import is used - err on the side of caution. 368 | // return true 369 | // } 370 | // 371 | // ast.Walk(visitFn(func(n ast.Node) { 372 | // sel, ok := n.(*ast.SelectorExpr) 373 | // if ok && isTopName(sel.X, name) { 374 | // used = true 375 | // } 376 | // }), f) 377 | // 378 | // return 379 | //} 380 | 381 | //type visitFn func(node ast.Node) 382 | // 383 | //func (fn visitFn) Visit(node ast.Node) ast.Visitor { 384 | // fn(node) 385 | // return fn 386 | //} 387 | 388 | // imports returns true if f imports path. 389 | func imports(f *ast.File, path string) bool { 390 | return importSpec(f, path) != nil 391 | } 392 | 393 | // importSpec returns the import spec if f imports path, 394 | // or nil otherwise. 395 | func importSpec(f *ast.File, path string) *ast.ImportSpec { 396 | for _, s := range f.Imports { 397 | if importPath(s) == path { 398 | return s 399 | } 400 | } 401 | return nil 402 | } 403 | 404 | // importPath returns the unquoted import path of s, 405 | // or "" if the path is not properly quoted. 406 | func importPath(s *ast.ImportSpec) string { 407 | t, err := strconv.Unquote(s.Path.Value) 408 | if err == nil { 409 | return t 410 | } 411 | return "" 412 | } 413 | 414 | // declImports reports whether gen contains an import of path. 415 | func declImports(gen *ast.GenDecl, path string) bool { 416 | if gen.Tok != token.IMPORT { 417 | return false 418 | } 419 | for _, spec := range gen.Specs { 420 | impspec := spec.(*ast.ImportSpec) 421 | if importPath(impspec) == path { 422 | return true 423 | } 424 | } 425 | return false 426 | } 427 | 428 | // matchLen returns the length of the longest path segment prefix shared by x and y. 429 | func matchLen(x, y string) int { 430 | n := 0 431 | for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ { 432 | if x[i] == '/' { 433 | n++ 434 | } 435 | } 436 | return n 437 | } 438 | 439 | //// isTopName returns true if n is a top-level unresolved identifier with the given name. 440 | //func isTopName(n ast.Expr, name string) bool { 441 | // id, ok := n.(*ast.Ident) 442 | // return ok && id.Name == name && id.Obj == nil 443 | //} 444 | // 445 | //// Imports returns the file imports grouped by paragraph. 446 | //func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec { 447 | // var groups [][]*ast.ImportSpec 448 | // 449 | // for _, decl := range f.Decls { 450 | // genDecl, ok := decl.(*ast.GenDecl) 451 | // if !ok || genDecl.Tok != token.IMPORT { 452 | // break 453 | // } 454 | // 455 | // group := []*ast.ImportSpec{} 456 | // 457 | // var lastLine int 458 | // for _, spec := range genDecl.Specs { 459 | // importSpec := spec.(*ast.ImportSpec) 460 | // pos := importSpec.Path.ValuePos 461 | // line := fset.Position(pos).Line 462 | // if lastLine > 0 && pos > 0 && line-lastLine > 1 { 463 | // groups = append(groups, group) 464 | // group = []*ast.ImportSpec{} 465 | // } 466 | // group = append(group, importSpec) 467 | // lastLine = line 468 | // } 469 | // groups = append(groups, group) 470 | // } 471 | // 472 | // return groups 473 | //} 474 | -------------------------------------------------------------------------------- /util/contrib.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | const ( 13 | fileDescriptorJson string = "descriptor.json" 14 | ) 15 | 16 | var contribDescriptors = []string{"descriptor.json", "activity.json", "trigger.json", "action.json"} 17 | 18 | // FlogoAppDescriptor is the descriptor for a Flogo application 19 | type FlogoContribDescriptor struct { 20 | Name string `json:"name"` 21 | Type string `json:"type"` 22 | Version string `json:"version"` 23 | Description string `json:"description"` 24 | Homepage string `json:"homepage"` 25 | Shim string `json:"shim"` 26 | Ref string `json:"ref"` //legacy 27 | 28 | IsLegacy bool `json:"-"` 29 | } 30 | 31 | type FlogoContribBundleDescriptor struct { 32 | Name string `json:"name"` 33 | Description string `json:"description"` 34 | Contribs []string `json:"contributions"` 35 | } 36 | 37 | func (d *FlogoContribDescriptor) GetContribType() string { 38 | parts := strings.Split(d.Type, ":") 39 | if len(parts) > 1 { 40 | return parts[1] 41 | } 42 | return "" 43 | } 44 | 45 | 46 | func GetContribDescriptorFromImport(depManager DepManager, contribImport Import) (*FlogoContribDescriptor, error) { 47 | 48 | contribPath, err := depManager.GetPath(contribImport) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | return GetContribDescriptor(contribPath) 54 | } 55 | 56 | func GetContribDescriptor(contribPath string) (*FlogoContribDescriptor, error) { 57 | 58 | var descriptorPath string 59 | oldDescriptor := false 60 | 61 | for _, descriptorName := range contribDescriptors { 62 | dPath := filepath.Join(contribPath, descriptorName) 63 | if _, err := os.Stat(dPath); err == nil { 64 | if descriptorName != "descriptor.json" { 65 | oldDescriptor = true 66 | } 67 | descriptorPath = dPath 68 | } 69 | } 70 | 71 | if descriptorPath == "" { 72 | //descriptor not found 73 | return nil, nil 74 | } 75 | 76 | desc, err := ReadContribDescriptor(descriptorPath) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | if desc.Ref != "" && oldDescriptor { 82 | desc.IsLegacy = true 83 | } 84 | 85 | return desc, nil 86 | } 87 | 88 | func ReadContribDescriptor(descriptorFile string) (*FlogoContribDescriptor, error) { 89 | 90 | descriptorJson, err := os.Open(descriptorFile) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | bytes, err := ioutil.ReadAll(descriptorJson) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | descriptor := &FlogoContribDescriptor{} 101 | 102 | err = json.Unmarshal(bytes, descriptor) 103 | if err != nil { 104 | return nil, fmt.Errorf("failed to parse descriptor '%s': %s", descriptorFile, err.Error()) 105 | } 106 | 107 | return descriptor, nil 108 | } 109 | 110 | func GetContribType(depManager DepManager, ref string) (string, error) { 111 | 112 | refAsFlogoImport, err := NewFlogoImportFromPath(ref) 113 | if err != nil { 114 | return "", err 115 | } 116 | 117 | impPath, err := depManager.GetPath(refAsFlogoImport)//(refAsFlogoImport) 118 | if err != nil { 119 | return "", err 120 | } 121 | var descriptorPath string 122 | 123 | if _, err := os.Stat(filepath.Join(impPath, fileDescriptorJson)); err == nil { 124 | descriptorPath = filepath.Join(impPath, fileDescriptorJson) 125 | 126 | } else if _, err := os.Stat(filepath.Join(impPath, "activity.json")); err == nil { 127 | descriptorPath = filepath.Join(impPath, "activity.json") 128 | } else if _, err := os.Stat(filepath.Join(impPath, "trigger.json")); err == nil { 129 | descriptorPath = filepath.Join(impPath, "trigger.json") 130 | } else if _, err := os.Stat(filepath.Join(impPath, "action.json")); err == nil { 131 | descriptorPath = filepath.Join(impPath, "action.json") 132 | } 133 | 134 | if _, err := os.Stat(descriptorPath); descriptorPath != "" && err == nil { 135 | 136 | desc, err := ReadContribDescriptor(descriptorPath) 137 | if err != nil { 138 | return "", err 139 | } 140 | 141 | return desc.Type, nil 142 | } 143 | 144 | return "", nil 145 | } 146 | -------------------------------------------------------------------------------- /util/engine.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | // PartialEngineDescriptor is the descriptor for a Flogo application 12 | type PartialEngineDescriptor struct { 13 | Imports []string `json:"imports"` 14 | Services []*EngineServiceDetails `json:"services"` 15 | } 16 | 17 | type EngineServiceDetails struct { 18 | Ref string 19 | } 20 | 21 | type EngineImportDetails struct { 22 | Imp Import 23 | TopLevel bool // a toplevel import i.e. from imports section 24 | ServiceRef bool // imports is used by a service 25 | 26 | HasAliasRef bool // imports alias is used by a contrib reference 27 | HasDirectRef bool // a direct reference exists for this import 28 | } 29 | 30 | type EngineImports struct { 31 | imports map[string]*EngineImportDetails 32 | orphanedRef map[string]void 33 | depManager DepManager 34 | } 35 | 36 | func (ai *EngineImports) addImports(imports []string) error { 37 | for _, anImport := range imports { 38 | flogoImport, err := ParseImport(anImport) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | if _, exists := ai.imports[flogoImport.GoImportPath()]; exists { 44 | //todo warn about duplicate import? 45 | continue 46 | } 47 | 48 | details := &EngineImportDetails{Imp: flogoImport, TopLevel:true} 49 | ai.imports[flogoImport.GoImportPath()] = details 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func (ai *EngineImports) addReference(ref string, isService bool) error { 56 | cleanedRef := strings.TrimSpace(ref) 57 | 58 | if cleanedRef[0] == '#' { 59 | 60 | alias := cleanedRef[1:] 61 | found := false 62 | for _, importDetails := range ai.imports { 63 | 64 | if importDetails.TopLevel { 65 | // alias refs can only be to toplevel imports 66 | if importDetails.Imp.CanonicalAlias() == alias { 67 | importDetails.HasAliasRef = true 68 | importDetails.ServiceRef = isService 69 | found = true 70 | break 71 | } 72 | } 73 | } 74 | 75 | if !found { 76 | ai.orphanedRef[cleanedRef] = void{} 77 | } 78 | 79 | } else { 80 | flogoImport, err := ParseImport(ref) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | if imp, exists := ai.imports[flogoImport.GoImportPath()]; exists { 86 | if !imp.TopLevel { 87 | //already accounted for 88 | return nil 89 | } 90 | 91 | imp.HasDirectRef = true 92 | imp.ServiceRef = true 93 | return nil 94 | } 95 | 96 | //doesn't exists so add new import 97 | details := &EngineImportDetails{Imp: flogoImport, ServiceRef:isService} 98 | ai.imports[flogoImport.GoImportPath()] = details 99 | } 100 | 101 | return nil 102 | } 103 | 104 | func (ai *EngineImports) GetOrphanedReferences() []string { 105 | var refs []string 106 | for ref := range ai.orphanedRef { 107 | refs = append(refs, ref) 108 | } 109 | 110 | return refs 111 | } 112 | 113 | func (ai *EngineImports) GetAllImports() []Import { 114 | var allImports []Import 115 | for _, details := range ai.imports { 116 | allImports = append(allImports, details.Imp) 117 | } 118 | 119 | return allImports 120 | } 121 | 122 | func (ai *EngineImports) GetAllImportDetails() []*EngineImportDetails { 123 | 124 | var allImports []*EngineImportDetails 125 | for _, details := range ai.imports { 126 | allImports = append(allImports, details) 127 | } 128 | 129 | return allImports 130 | } 131 | 132 | func GetEngineImports(engJsonFile string, depManager DepManager) (*EngineImports, error) { 133 | engJson, err := os.Open(engJsonFile) 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | bytes, err := ioutil.ReadAll(engJson) 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | engDesc := &PartialEngineDescriptor{} 144 | err = json.Unmarshal(bytes, engDesc) 145 | if err != nil { 146 | fmt.Fprintf(os.Stderr, "Unable to unmarshal flogo engine json: %s", engJsonFile) 147 | return nil, err 148 | } 149 | 150 | imports := make(map[string]void) 151 | 152 | if len(engDesc.Imports) > 0 { 153 | for _, imp := range engDesc.Imports { 154 | imports[imp] = void{} 155 | } 156 | } 157 | 158 | if len(engDesc.Services) > 0 { 159 | for _, service := range engDesc.Services { 160 | imports[service.Ref] = void{} 161 | } 162 | } 163 | 164 | 165 | ai := &EngineImports{depManager: depManager} 166 | ai.imports = make(map[string]*EngineImportDetails) 167 | ai.orphanedRef = make(map[string]void) 168 | 169 | err = ai.addImports(engDesc.Imports) 170 | if err != nil { 171 | return nil, err 172 | } 173 | 174 | // add service refs/imports 175 | for _, serviceDetails := range engDesc.Services { 176 | err := ai.addReference(serviceDetails.Ref, true) 177 | if err != nil { 178 | return nil, err 179 | } 180 | } 181 | 182 | return ai, err 183 | } 184 | -------------------------------------------------------------------------------- /util/env.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "strings" 4 | 5 | func ReplaceEnvValue(env []string, envKey string, newValue string) []string { 6 | for key, entry := range env { 7 | 8 | if strings.HasPrefix(entry, envKey+"=") { 9 | env[key] = envKey + "=" + newValue 10 | break 11 | } 12 | } 13 | 14 | return env 15 | } 16 | -------------------------------------------------------------------------------- /util/file.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | "path" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | func IsRemote(path string) bool { 15 | return strings.HasPrefix(path, "http") 16 | } 17 | 18 | func LoadRemoteFile(sourceURL string) (string, error) { 19 | 20 | resp, err := http.Get(sourceURL) 21 | if err != nil { 22 | return "", err 23 | } 24 | 25 | defer resp.Body.Close() 26 | 27 | buf, err := ioutil.ReadAll(resp.Body) 28 | if err != nil { 29 | return "", err 30 | } 31 | 32 | return string(buf), nil 33 | } 34 | 35 | func LoadLocalFile(path string) (string, error) { 36 | 37 | buf, err := ioutil.ReadFile(path) 38 | if err != nil { 39 | return "", err 40 | } 41 | 42 | return string(buf), nil 43 | } 44 | 45 | func Rename(srcPath string) error { 46 | oldPath := fmt.Sprintf("%s.old", srcPath) 47 | _ = os.Remove(oldPath) 48 | 49 | err := os.Rename(srcPath, oldPath) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | return nil 55 | } 56 | 57 | func CopyFile(srcFile, destFile string) error { 58 | input, err := ioutil.ReadFile(srcFile) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | err = ioutil.WriteFile(destFile, input, 0644) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | return nil 69 | } 70 | 71 | func Copy(src, dst string, copyMode bool) error { 72 | info, err := os.Lstat(src) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | if info.IsDir() { 78 | return copyDir(src, dst, info, copyMode) 79 | } 80 | 81 | return copyFile(src, dst, info, copyMode) 82 | } 83 | 84 | func copyDir(srcDir, dstDir string, info os.FileInfo, copyMode bool) error { 85 | 86 | if err := os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { 87 | return err 88 | } 89 | 90 | if copyMode { 91 | defer os.Chmod(dstDir, info.Mode()) 92 | } 93 | 94 | items, err := ioutil.ReadDir(srcDir) 95 | if err != nil { 96 | return err 97 | } 98 | 99 | for _, item := range items { 100 | 101 | srcPath := path.Join(srcDir, item.Name()) 102 | dstPath := path.Join(dstDir, item.Name()) 103 | 104 | if item.IsDir() { 105 | if err = copyDir(srcPath, dstPath, item, copyMode); err != nil { 106 | return err 107 | } 108 | } else { 109 | if err = copyFile(srcPath, dstPath, item, copyMode); err != nil { 110 | return err 111 | } 112 | } 113 | } 114 | 115 | return nil 116 | } 117 | 118 | func copyFile(src, dst string, srcInfo os.FileInfo, copyMode bool) error { 119 | var err error 120 | 121 | if err = os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil { 122 | return err 123 | } 124 | 125 | var sf *os.File 126 | if sf, err = os.Open(src); err != nil { 127 | return err 128 | } 129 | defer sf.Close() 130 | 131 | var df *os.File 132 | if df, err = os.Create(dst); err != nil { 133 | return err 134 | } 135 | defer df.Close() 136 | 137 | if err = os.Chmod(sf.Name(), srcInfo.Mode()); err != nil { 138 | return err 139 | } 140 | 141 | if _, err = io.Copy(df, sf); err != nil { 142 | return err 143 | } 144 | 145 | return nil 146 | } 147 | 148 | func FileExists(path string) bool { 149 | info, err := os.Stat(path) 150 | if os.IsNotExist(err) { 151 | return false 152 | } 153 | return !info.IsDir() 154 | } 155 | 156 | func DirExists(path string) bool { 157 | info, err := os.Stat(path) 158 | if os.IsNotExist(err) { 159 | return false 160 | } 161 | return info.IsDir() 162 | } 163 | 164 | func DeleteFile(path string) error { 165 | 166 | if _, err := os.Stat(path); err == nil { 167 | err = os.Remove(path) 168 | if err != nil { 169 | return err 170 | } 171 | } 172 | 173 | return nil 174 | } -------------------------------------------------------------------------------- /util/flogo.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // ParseAppDescriptor parse the application descriptor 8 | func ParseAppDescriptor(appJson string) (*FlogoAppDescriptor, error) { 9 | descriptor := &FlogoAppDescriptor{} 10 | 11 | err := json.Unmarshal([]byte(appJson), descriptor) 12 | 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | return descriptor, nil 18 | } 19 | 20 | // FlogoAppDescriptor is the descriptor for a Flogo application 21 | type FlogoAppDescriptor struct { 22 | Name string `json:"name"` 23 | Type string `json:"type"` 24 | Version string `json:"version"` 25 | Description string `json:"description"` 26 | AppModel string `json:"appModel,omitempty"` 27 | Imports []string `json:"imports"` 28 | 29 | Triggers []*FlogoTriggerConfig `json:"triggers"` 30 | } 31 | 32 | type FlogoTriggerConfig struct { 33 | Id string `json:"id"` 34 | Ref string `json:"ref"` 35 | Type string `json:"type"` 36 | } 37 | -------------------------------------------------------------------------------- /util/flogo_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestName(t *testing.T) { 10 | line := ` "ref":"github.com/TIBCOSoftware/flogo-contrib/activity/log",` 11 | 12 | if idx := strings.Index(line, "\"ref\""); idx > -1 { 13 | 14 | fmt.Printf("line: '%s'\n", line) 15 | 16 | startPkgIdx := strings.Index(line[idx+6:], "\"") 17 | 18 | fmt.Printf("line frag: '%s'\n", line[idx+6+startPkgIdx:]) 19 | pkg := strings.Split(line[idx+6+startPkgIdx:], "\"")[1] 20 | //pkg := strings.Split(",")[0] 21 | //pkg := strings.Split(line, ":")[1] 22 | //pkg = strings.TrimSpace(pkg) 23 | //pkg = pkg[1 : len(pkg)-2] 24 | fmt.Printf("package: '%s'\n", pkg) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /util/gosrchelper.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/coreos/go-semver/semver" 12 | ) 13 | 14 | var goPathCached string 15 | 16 | func FindOldPackageSrc(pkg string) (srcPath, srcVer string, err error) { 17 | 18 | goPath := GetGoPath() 19 | 20 | pkgParts := strings.Split(pkg, "/") 21 | path := filepath.Join(goPath, "src", filepath.Join(pkgParts...)) 22 | 23 | if _, e := os.Stat(path); !os.IsNotExist(e) { 24 | // path/to/whatever exists 25 | v := GetPackageVersionFromSource(pkg) 26 | 27 | return path, v, nil 28 | } 29 | 30 | return "", "", newPkgNotFoundError(pkg) 31 | } 32 | 33 | func FindGoModPackageSrc(pkg string, version string, latest bool) (srcPath, srcVer string, err error) { 34 | 35 | pkgParts := strings.Split(pkg, "/") 36 | if len(pkgParts) < 2 { 37 | return "", "", fmt.Errorf("invalid package: %s", pkg) 38 | } 39 | 40 | name := pkgParts[len(pkgParts)-1] 41 | path := pkgParts[:len(pkgParts)-1] 42 | 43 | goPath := GetGoPath() 44 | flogoPkgPath := filepath.Join(goPath, "pkg", "mod", filepath.Join(path...)) 45 | 46 | if _, e := os.Stat(flogoPkgPath); os.IsNotExist(e) { 47 | return "", "", newPkgNotFoundError(pkg) 48 | } 49 | 50 | var files []string 51 | 52 | err = filepath.Walk(flogoPkgPath, visit(name, &files)) 53 | if err != nil { 54 | return "", "", newPkgNotFoundError(pkg) 55 | } 56 | 57 | if latest { 58 | 59 | lf := "" 60 | var lv *semver.Version 61 | 62 | for _, file := range files { 63 | 64 | parts := strings.SplitN(file, "@v", 2) 65 | 66 | if lf == "" { 67 | lf = file 68 | lv, err = semver.NewVersion(parts[1]) 69 | if err != nil { 70 | return "", "", err 71 | } 72 | continue 73 | } 74 | 75 | sv, err := semver.NewVersion(parts[1]) 76 | if err != nil { 77 | return "", "", err 78 | } 79 | 80 | if lv.LessThan(*sv) { 81 | lf = file 82 | lv = sv 83 | } 84 | } 85 | 86 | if lf == "" { 87 | return "", "", newPkgNotFoundError(pkg) 88 | } 89 | 90 | return lf, lv.String(), nil 91 | } else { 92 | 93 | for _, file := range files { 94 | 95 | parts := strings.SplitN(file, "@v", 2) 96 | if parts[1] == version { 97 | return file, version, nil 98 | } 99 | } 100 | } 101 | 102 | return "", "", newPkgNotFoundError(pkg) 103 | } 104 | 105 | func visit(name string, files *[]string) filepath.WalkFunc { 106 | return func(path string, info os.FileInfo, err error) error { 107 | if err != nil { 108 | log.Fatal(err) 109 | } 110 | if !info.IsDir() { 111 | return nil 112 | } 113 | 114 | if strings.HasPrefix(info.Name(), name+"@v") { 115 | *files = append(*files, path) 116 | } 117 | 118 | return nil 119 | } 120 | } 121 | 122 | func GetGoPath() string { 123 | 124 | if goPathCached != "" { 125 | return goPathCached 126 | } 127 | 128 | set := false 129 | goPathCached, set = os.LookupEnv("GOPATH") 130 | if !set { 131 | out, err := exec.Command("go", "env", "GOPATH").Output() 132 | if err != nil { 133 | log.Fatal(err) 134 | } 135 | goPathCached = strings.TrimSuffix(string(out), "\n") 136 | } 137 | 138 | return goPathCached 139 | } 140 | 141 | func newPkgNotFoundError(pkg string) error { 142 | return &pkgNotFoundError{pkg: pkg} 143 | } 144 | 145 | type pkgNotFoundError struct { 146 | pkg string 147 | } 148 | 149 | func (e *pkgNotFoundError) Error() string { 150 | return fmt.Sprintf("Package '%s' not found", e.pkg) 151 | } 152 | 153 | func IsPkgNotFoundError(err error) bool { 154 | _, ok := err.(*pkgNotFoundError) 155 | return ok 156 | } -------------------------------------------------------------------------------- /util/gosrchelper_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestFindGoModPackageSrc(t *testing.T) { 10 | str, ver, err := FindGoModPackageSrc("github.com/project-flogo/core", "", true) 11 | if err != nil { 12 | fmt.Println("err:", err) 13 | t.FailNow() 14 | } 15 | 16 | fmt.Println("path: ", str) 17 | fmt.Println("ver: ", ver) 18 | } 19 | 20 | 21 | func TestFindOldPackageSrc(t *testing.T) { 22 | str, ver, err := FindOldPackageSrc("github.com/project-flogo/cli") 23 | if err != nil { 24 | fmt.Println("err:", err) 25 | t.FailNow() 26 | } 27 | 28 | fmt.Println("path: ", str) 29 | fmt.Println("ver: ", ver) 30 | } 31 | 32 | 33 | func TestFindGoModPackageSrcNotFound(t *testing.T) { 34 | _, _, err := FindGoModPackageSrc("github.com/project-blah/core", "", true) 35 | assert.True(t, IsPkgNotFoundError(err)) 36 | fmt.Println("err: ", err) 37 | } 38 | 39 | 40 | func TestFindOldPackageSrcNotFound(t *testing.T) { 41 | _, _, err := FindOldPackageSrc("github.com/project-blah/core") 42 | assert.True(t, IsPkgNotFoundError(err)) 43 | fmt.Println("err: ", err) 44 | } 45 | -------------------------------------------------------------------------------- /util/imports.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "path" 7 | "regexp" 8 | ) 9 | 10 | /* util.Import struct defines the different fields which can be extracted from a Flogo import 11 | these imports are stored in flogo.json in the "imports" array, for instance: 12 | 13 | "imports": [ 14 | "github.com/project-flogo/contrib@v0.9.0-alpha.4:/activity/log", 15 | "github.com/project-flogo/contrib/activity/rest@v0.9.0" 16 | "rest_activity github.com/project-flogo/contrib@v0.9.0:/activity/rest", 17 | "rest_trigger github.com/project-flogo/contrib:/trigger/rest", 18 | "github.com/project-flogo/flow" 19 | ] 20 | 21 | */ 22 | 23 | type FlogoImport struct { 24 | modulePath string 25 | relativeImportPath string 26 | version string 27 | alias string 28 | } 29 | 30 | func NewFlogoImportFromPath(flogoImportPath string) (Import, error) { 31 | flogoImport, err := ParseImport(flogoImportPath) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return flogoImport, nil 36 | } 37 | 38 | func NewFlogoImport(modulePath, relativeImportPath, version, alias string) Import { 39 | return &FlogoImport{modulePath: modulePath, relativeImportPath: relativeImportPath, version: version, alias: alias} 40 | } 41 | 42 | func NewFlogoImportWithVersion(imp Import, version string) Import { 43 | return &FlogoImport{modulePath: imp.ModulePath(), relativeImportPath: imp.RelativeImportPath(), version: version, alias: imp.Alias()} 44 | } 45 | 46 | type Import interface { 47 | fmt.Stringer 48 | 49 | ModulePath() string 50 | RelativeImportPath() string 51 | Version() string 52 | Alias() string 53 | 54 | CanonicalImport() string // canonical import is used in flogo.json imports array and to check for equality of imports 55 | GoImportPath() string // the import path used in .go files 56 | GoGetImportPath() string // the import path used by "go get" command 57 | GoModImportPath() string // the import path used by "go mod edit" command 58 | IsClassic() bool // an import is "classic" if it has no : character separator, hence no relative import path 59 | CanonicalAlias() string // canonical alias is the alias used in the flogo.json 60 | } 61 | 62 | type Imports []Import 63 | 64 | func (flogoImport *FlogoImport) ModulePath() string { 65 | return flogoImport.modulePath 66 | } 67 | 68 | func (flogoImport *FlogoImport) RelativeImportPath() string { 69 | return flogoImport.relativeImportPath 70 | } 71 | 72 | func (flogoImport *FlogoImport) Version() string { 73 | return flogoImport.version 74 | } 75 | 76 | func (flogoImport *FlogoImport) Alias() string { 77 | return flogoImport.alias 78 | } 79 | 80 | func (flogoImport *FlogoImport) CanonicalImport() string { 81 | alias := "" 82 | if flogoImport.alias != "" { 83 | alias = flogoImport.alias + " " 84 | } 85 | version := "" 86 | if flogoImport.version != "" { 87 | version = "@" + flogoImport.version 88 | } 89 | relativeImportPath := "" 90 | if flogoImport.relativeImportPath != "" { 91 | relativeImportPath = ":" + flogoImport.relativeImportPath 92 | } 93 | 94 | return alias + flogoImport.modulePath + version + relativeImportPath 95 | } 96 | 97 | func (flogoImport *FlogoImport) CanonicalAlias() string { 98 | if flogoImport.alias != "" { 99 | return flogoImport.alias 100 | } else { 101 | return path.Base(flogoImport.GoImportPath()) 102 | } 103 | } 104 | 105 | func (flogoImport *FlogoImport) GoImportPath() string { 106 | return flogoImport.modulePath + flogoImport.relativeImportPath 107 | } 108 | 109 | func (flogoImport *FlogoImport) GoGetImportPath() string { 110 | version := "@latest" 111 | if flogoImport.version != "" { 112 | version = "@" + flogoImport.version 113 | } 114 | return flogoImport.modulePath + flogoImport.relativeImportPath + version 115 | } 116 | 117 | func (flogoImport *FlogoImport) GoModImportPath() string { 118 | version := "@latest" 119 | if flogoImport.version != "" { 120 | version = "@" + flogoImport.version 121 | } 122 | return flogoImport.modulePath + version 123 | } 124 | 125 | func (flogoImport *FlogoImport) IsClassic() bool { 126 | return flogoImport.relativeImportPath == "" 127 | } 128 | 129 | func (flogoImport *FlogoImport) String() string { 130 | version := "" 131 | if flogoImport.version != "" { 132 | version = " " + flogoImport.version 133 | } 134 | relativeImportPath := "" 135 | if flogoImport.relativeImportPath != "" { 136 | relativeImportPath = flogoImport.relativeImportPath 137 | } 138 | 139 | return flogoImport.modulePath + relativeImportPath + version 140 | } 141 | 142 | var flogoImportPattern = regexp.MustCompile(`^(([^ ]*)[ ]+)?([^@:]*)@?([^:]*)?:?(.*)?$`) // extract import path even if there is an alias and/or a version 143 | 144 | func ParseImports(flogoImports []string) (Imports, error) { 145 | var result Imports 146 | 147 | for _, flogoImportPath := range flogoImports { 148 | flogoImport, err := ParseImport(flogoImportPath) 149 | if err != nil { 150 | return nil, err 151 | } 152 | result = append(result, flogoImport) 153 | } 154 | 155 | return result, nil 156 | } 157 | 158 | func ParseImport(flogoImport string) (Import, error) { 159 | if !flogoImportPattern.MatchString(flogoImport) { 160 | return nil, errors.New(fmt.Sprintf("The Flogo import '%s' cannot be parsed.", flogoImport)) 161 | } 162 | 163 | matches := flogoImportPattern.FindStringSubmatch(flogoImport) 164 | 165 | result := &FlogoImport{modulePath: matches[3], relativeImportPath: matches[5], version: matches[4], alias: matches[2]} 166 | 167 | return result, nil 168 | } 169 | -------------------------------------------------------------------------------- /util/mod.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "os" 12 | "os/exec" 13 | "path/filepath" 14 | "regexp" 15 | "strings" 16 | 17 | "github.com/msoap/byline" 18 | ) 19 | 20 | type DepManager interface { 21 | Init() error 22 | AddDependency(flogoImport Import) error 23 | GetPath(flogoImport Import) (string, error) 24 | AddReplacedContribForBuild() error 25 | InstallReplacedPkg(string, string) error 26 | GetAllImports() (map[string]Import, error) 27 | } 28 | 29 | func NewDepManager(sourceDir string) DepManager { 30 | return &ModDepManager{srcDir: sourceDir, localMods: make(map[string]string)} 31 | } 32 | 33 | type ModDepManager struct { 34 | srcDir string 35 | localMods map[string]string 36 | } 37 | 38 | func (m *ModDepManager) Init() error { 39 | 40 | err := ExecCmd(exec.Command("go", "mod", "init", "main"), m.srcDir) 41 | if err == nil { 42 | return err 43 | } 44 | 45 | return nil 46 | } 47 | 48 | func (m *ModDepManager) AddDependency(flogoImport Import) error { 49 | 50 | // todo: optimize the following 51 | 52 | // use "go mod edit" (instead of "go get") as first method 53 | err := ExecCmd(exec.Command("go", "mod", "edit", "-require", flogoImport.GoModImportPath()), m.srcDir) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | 59 | err = ExecCmd(exec.Command("go", "mod", "verify"), m.srcDir) 60 | if err == nil { 61 | err = ExecCmd(exec.Command("go", "mod", "download", flogoImport.ModulePath()), m.srcDir) 62 | } 63 | 64 | if err != nil { 65 | // if the resolution fails and the Flogo import is "classic" 66 | // (meaning it does not separate module path from Go import path): 67 | // 1. remove the import manually ("go mod edit -droprequire") would fail 68 | // 2. try with "go get" instead 69 | if flogoImport.IsClassic() { 70 | m.RemoveImport(flogoImport) 71 | 72 | err = ExecCmd(exec.Command("go", "get", flogoImport.GoGetImportPath()), m.srcDir) 73 | } 74 | } 75 | 76 | if err != nil { 77 | return err 78 | } 79 | 80 | return nil 81 | } 82 | 83 | // GetPath gets the path of where the 84 | func (m *ModDepManager) GetPath(flogoImport Import) (string, error) { 85 | 86 | currentDir, err := os.Getwd() 87 | if err != nil { 88 | return "", err 89 | } 90 | 91 | pkg := flogoImport.ModulePath() 92 | 93 | path, ok := m.localMods[pkg] 94 | if ok && path != "" { 95 | 96 | return path, nil 97 | } 98 | defer os.Chdir(currentDir) 99 | 100 | os.Chdir(m.srcDir) 101 | 102 | file, err := os.Open(filepath.Join(m.srcDir, "go.mod")) 103 | defer file.Close() 104 | 105 | var pathForPartial string 106 | 107 | scanner := bufio.NewScanner(file) 108 | for scanner.Scan() { 109 | 110 | line := scanner.Text() 111 | reqComponents := strings.Fields(line) 112 | //It is the line in the go.mod which is not useful, so ignore. 113 | if len(reqComponents) < 2 || (reqComponents[0] == "require" && reqComponents[1] == "(") { 114 | continue 115 | } 116 | 117 | //typically package is 1st component and version is the 2nd component 118 | reqPkg := reqComponents[0] 119 | version := reqComponents[1] 120 | if reqComponents[0] == "require" { 121 | //starts with require, so package is 2nd component and version is the 3rd component 122 | reqPkg = reqComponents[1] 123 | version = reqComponents[2] 124 | } 125 | 126 | if strings.HasPrefix(pkg, reqPkg) { 127 | 128 | hasFull := strings.Contains(line, pkg) 129 | tempPath := strings.Split(reqPkg, "/") 130 | 131 | tempPath = toLower(tempPath) 132 | lastIdx := len(tempPath) - 1 133 | 134 | tempPath[lastIdx] = tempPath[lastIdx] + "@" + version 135 | 136 | pkgPath := filepath.Join(tempPath...) 137 | 138 | if !hasFull { 139 | remaining := pkg[len(reqPkg):] 140 | tempPath = strings.Split(remaining, "/") 141 | remainingPath := filepath.Join(tempPath...) 142 | 143 | pathForPartial = filepath.Join(os.Getenv("GOPATH"), "pkg", "mod", pkgPath, remainingPath) 144 | } else { 145 | return filepath.Join(os.Getenv("GOPATH"), "pkg", "mod", pkgPath, flogoImport.RelativeImportPath()), nil 146 | } 147 | } 148 | } 149 | return pathForPartial, nil 150 | } 151 | 152 | func (m *ModDepManager) RemoveImport(flogoImport Import) error { 153 | 154 | currentDir, err := os.Getwd() 155 | if err != nil { 156 | return err 157 | } 158 | 159 | modulePath := flogoImport.ModulePath() 160 | 161 | defer os.Chdir(currentDir) 162 | 163 | os.Chdir(m.srcDir) 164 | 165 | file, err := os.Open(filepath.Join(m.srcDir, "go.mod")) 166 | if err != nil { 167 | return err 168 | } 169 | defer file.Close() 170 | 171 | modulePath = strings.Replace(modulePath, "/", "\\/", -1) 172 | modulePath = strings.Replace(modulePath, ".", "\\.", -1) 173 | importRegex := regexp.MustCompile(`\s*` + modulePath + `\s+` + flogoImport.Version() + `.*`) 174 | 175 | lr := byline.NewReader(file) 176 | 177 | lr.MapString(func(line string) string { 178 | if importRegex.MatchString(line) { 179 | return "" 180 | } else { 181 | return line 182 | } 183 | }) 184 | 185 | updatedGoMod, err := lr.ReadAll() 186 | if err != nil { 187 | return err 188 | } 189 | 190 | file, err = os.Create(filepath.Join(m.srcDir, "go.mod")) 191 | if err != nil { 192 | return err 193 | } 194 | defer file.Close() 195 | 196 | file.Write(updatedGoMod) 197 | 198 | return nil 199 | } 200 | func (m *ModDepManager) GetAllImports() (map[string]Import, error) { 201 | file, err := ioutil.ReadFile(filepath.Join(m.srcDir, "go.mod")) 202 | if err != nil { 203 | return nil, err 204 | } 205 | 206 | content := string(file) 207 | 208 | imports := strings.Split(content[strings.Index(content, "(")+1:strings.Index(content, ")")], "\n") 209 | result := make(map[string]Import) 210 | 211 | for _, pkg := range imports { 212 | if pkg != " " && pkg != "" { 213 | 214 | mods := strings.Split(strings.TrimSpace(pkg), " ") 215 | 216 | modImport, err := ParseImport(strings.Join(mods[:2], "@")) 217 | if err != nil { 218 | return nil, err 219 | } 220 | 221 | result[modImport.GoImportPath()] = modImport 222 | } 223 | } 224 | 225 | return result, nil 226 | } 227 | 228 | //This function converts capotal letters in package name 229 | // to !(smallercase). Eg C => !c . As this is the way 230 | // go.mod saves every repository in the $GOPATH/pkg/mod. 231 | func toLower(s []string) []string { 232 | result := make([]string, len(s)) 233 | for i := 0; i < len(s); i++ { 234 | var b bytes.Buffer 235 | for _, c := range s[i] { 236 | if c >= 65 && c <= 90 { 237 | b.WriteRune(33) 238 | b.WriteRune(c + 32) 239 | } else { 240 | b.WriteRune(c) 241 | } 242 | } 243 | result[i] = b.String() 244 | } 245 | return result 246 | } 247 | 248 | var verbose = false 249 | 250 | func SetVerbose(enable bool) { 251 | verbose = enable 252 | } 253 | 254 | func Verbose() bool { 255 | return verbose 256 | } 257 | 258 | func ExecCmd(cmd *exec.Cmd, workingDir string) error { 259 | 260 | if workingDir != "" { 261 | cmd.Dir = workingDir 262 | } 263 | 264 | var out bytes.Buffer 265 | 266 | if verbose { 267 | cmd.Stdout = os.Stdout 268 | cmd.Stderr = os.Stderr 269 | } else { 270 | cmd.Stdout = nil 271 | cmd.Stderr = &out 272 | } 273 | 274 | err := cmd.Run() 275 | 276 | if err != nil { 277 | return fmt.Errorf(string(out.Bytes())) 278 | } 279 | 280 | return nil 281 | } 282 | 283 | func (m *ModDepManager) AddReplacedContribForBuild() error { 284 | 285 | err := ExecCmd(exec.Command("go", "mod", "download"), m.srcDir) 286 | if err != nil { 287 | return err 288 | } 289 | 290 | text, err := ioutil.ReadFile(filepath.Join(m.srcDir, "go.mod")) 291 | if err != nil { 292 | return err 293 | } 294 | 295 | data := string(text) 296 | 297 | index := strings.Index(data, "replace") 298 | if index != -1 { 299 | localModules := strings.Split(data[index-1:], "\n") 300 | 301 | for _, val := range localModules { 302 | if val != "" { 303 | mods := strings.Split(val, " ") 304 | //If the length of mods is more than 4 it contains the versions of package 305 | //so it is stating to use different version of pkg rather than 306 | // the local pkg. 307 | if len(mods) < 5 { 308 | 309 | m.localMods[mods[1]] = mods[3] 310 | } else { 311 | 312 | m.localMods[mods[1]] = filepath.Join(os.Getenv("GOPATH"), "pkg", "mod", mods[3]+"@"+mods[4]) 313 | } 314 | 315 | } 316 | 317 | } 318 | return nil 319 | } 320 | return nil 321 | } 322 | 323 | func (m *ModDepManager) InstallReplacedPkg(pkg1 string, pkg2 string) error { 324 | 325 | m.localMods[pkg1] = pkg2 326 | 327 | f, err := os.OpenFile(filepath.Join(m.srcDir, "go.mod"), os.O_APPEND|os.O_WRONLY, 0777) 328 | if err != nil { 329 | return err 330 | } 331 | defer f.Close() 332 | 333 | if _, err = f.WriteString(fmt.Sprintf("replace %v => %v", pkg1, pkg2)); err != nil { 334 | return err 335 | } 336 | 337 | err = ExecCmd(exec.Command("go", "mod", "download"), m.srcDir) 338 | if err != nil { 339 | return err 340 | } 341 | return nil 342 | } 343 | 344 | type Resp struct { 345 | Name string `json:"name"` 346 | } 347 | 348 | func getLatestVersion(path string) string { 349 | 350 | //To get the latest version number use the GitHub API. 351 | resp, err := http.Get("https://api.github.com/repos/TIBCOSoftware/flogo-contrib/releases/latest") 352 | if err != nil { 353 | log.Fatalln(err) 354 | } 355 | 356 | body, err := ioutil.ReadAll(resp.Body) 357 | if err != nil { 358 | log.Fatalln(err) 359 | } 360 | 361 | var result Resp 362 | 363 | err = json.Unmarshal(body, &result) 364 | if err != nil { 365 | return "" 366 | } 367 | 368 | return result.Name 369 | 370 | } 371 | -------------------------------------------------------------------------------- /util/version.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "regexp" 9 | "strings" 10 | "text/template" 11 | "time" 12 | ) 13 | 14 | const ( 15 | cliPackage = "github.com/project-flogo/cli" 16 | ) 17 | 18 | func GetCLIInfo() (string, string, error) { 19 | 20 | path, ver, err := FindOldPackageSrc(cliPackage) 21 | 22 | if IsPkgNotFoundError(err) { 23 | //must be using the new go mod layout 24 | path, ver, err = FindGoModPackageSrc(cliPackage, "", true) 25 | if err != nil { 26 | workingDir, _ := os.Getwd() 27 | ver = GetPackageVersionFromGit(workingDir) 28 | } 29 | } 30 | 31 | return path, ver, err 32 | } 33 | 34 | // retrieve the package version from source in GOPATH using "git describe" command 35 | func GetPackageVersionFromSource(pkg string) string { 36 | gopath := GetGoPath() 37 | 38 | pkgParts := strings.Split(pkg, "/") 39 | 40 | return GetPackageVersionFromGit(filepath.Join(gopath, "src", filepath.Join(pkgParts...))) 41 | } 42 | 43 | // retrieve the package version from source in a directory using "git describe" command 44 | func GetPackageVersionFromGit(dir string) string { 45 | re := regexp.MustCompile("\\n") 46 | 47 | cmd := exec.Command("git", "describe", "--tags", "--dirty", "--always") 48 | cmd.Env = append(os.Environ()) 49 | 50 | cmd.Dir = dir 51 | 52 | out, err := cmd.Output() // execute "git describe" 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | fc := re.ReplaceAllString(string(out), "") 57 | 58 | if len(fc) > 1 { 59 | return fc[1:] 60 | } 61 | 62 | return fc 63 | } 64 | 65 | func CreateVersionFile(cmdPath, currentVersion string) error { 66 | 67 | f, err := os.Create(filepath.Join(cmdPath, "currentversion.go")) 68 | if err != nil { 69 | log.Fatal(err) 70 | return nil 71 | } 72 | defer f.Close() 73 | 74 | _ = packageTemplate.Execute(f, struct { 75 | Timestamp time.Time 76 | Version string 77 | }{ 78 | Timestamp: time.Now(), 79 | Version: currentVersion, 80 | }) 81 | 82 | return nil 83 | } 84 | 85 | var packageTemplate = template.Must(template.New("").Parse(`// Generated Code; DO NOT EDIT. 86 | // {{ .Timestamp }} 87 | package main 88 | 89 | func init() { 90 | Version = "{{ .Version }}" 91 | } 92 | `)) 93 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import _ "embed" 4 | 5 | //go:embed VERSION 6 | var version string 7 | 8 | // Version will return the release version 9 | func Version() string { 10 | return version 11 | } 12 | --------------------------------------------------------------------------------