├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── go.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── AUTHORS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── doc.go ├── go.mod ├── go.sum ├── newreleases ├── cmd │ ├── auth.go │ ├── auth_test.go │ ├── client.go │ ├── cmd.go │ ├── cmd_test.go │ ├── configure.go │ ├── configure_test.go │ ├── discord.go │ ├── discord_test.go │ ├── export_test.go │ ├── get_auth_key.go │ ├── get_auth_key_test.go │ ├── hangouts_chat.go │ ├── hangouts_chat_test.go │ ├── help.go │ ├── help_test.go │ ├── matrix.go │ ├── matrix_test.go │ ├── mattermost.go │ ├── mattermost_test.go │ ├── microsoft_teams.go │ ├── microsoft_teams_test.go │ ├── project.go │ ├── project_add.go │ ├── project_add_test.go │ ├── project_get.go │ ├── project_get_test.go │ ├── project_list.go │ ├── project_list_test.go │ ├── project_remove.go │ ├── project_remove_test.go │ ├── project_search.go │ ├── project_search_test.go │ ├── project_test.go │ ├── project_update.go │ ├── project_update_test.go │ ├── providers.go │ ├── providers_test.go │ ├── release.go │ ├── release_test.go │ ├── rocketchat.go │ ├── rocketchat_test.go │ ├── slack.go │ ├── slack_test.go │ ├── tag.go │ ├── tag_add.go │ ├── tag_add_test.go │ ├── tag_get.go │ ├── tag_get_test.go │ ├── tag_list.go │ ├── tag_list_test.go │ ├── tag_remove.go │ ├── tag_remove_test.go │ ├── tag_test.go │ ├── tag_update.go │ ├── tag_update_test.go │ ├── telegram.go │ ├── telegram_test.go │ ├── terminal.go │ ├── version.go │ ├── version_test.go │ ├── webhook.go │ └── webhook_test.go └── main.go └── version.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push, pull_request] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, macos-latest, windows-latest] 11 | 12 | steps: 13 | 14 | - name: Set up Go 15 | uses: actions/setup-go@v5 16 | with: 17 | go-version: "1.24" 18 | 19 | - name: Checkout 20 | uses: actions/checkout@v1 21 | with: 22 | fetch-depth: 1 23 | 24 | - name: Cache Go modules 25 | uses: actions/cache@v4 26 | with: 27 | path: ~/go/pkg/mod 28 | key: ${{ runner.os }}-build-${{ hashFiles('**/go.sum') }} 29 | restore-keys: | 30 | ${{ runner.OS }}-build-${{ env.cache-name }}- 31 | ${{ runner.OS }}-build- 32 | ${{ runner.OS }}- 33 | 34 | - name: Lint 35 | uses: golangci/golangci-lint-action@v4 36 | with: 37 | version: v1.64.5 38 | 39 | - name: Vet 40 | if: matrix.os == 'ubuntu-latest' 41 | run: go vet -v ./... 42 | 43 | - name: Build 44 | env: 45 | CGO_ENABLED: 0 46 | run: go build -ldflags "-s -w" ./... 47 | 48 | - name: Test 49 | run: go test -v -race ./... -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - '**' 7 | tags: 8 | - 'v*.*.*' 9 | 10 | jobs: 11 | goreleaser: 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - name: Checkout 16 | uses: actions/checkout@v1 17 | 18 | - name: Set up Go 19 | uses: actions/setup-go@v1 20 | with: 21 | go-version: '1.22' 22 | 23 | - name: Run GoReleaser 24 | uses: goreleaser/goreleaser-action@v1 25 | with: 26 | version: latest 27 | args: release --rm-dist 28 | key: ${{ secrets.YOUR_PRIVATE_KEY }} 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | HOMEBREW_CMD_TAP_TOKEN: ${{ secrets.HOMEBREW_CMD_TAP_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /.idea 3 | /.vscode 4 | 5 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 6 | *.o 7 | *.a 8 | *.so 9 | 10 | # Folders 11 | _obj 12 | _test 13 | 14 | # Architecture specific extensions/prefixes 15 | *.[568vq] 16 | [568vq].out 17 | 18 | *.cgo1.go 19 | *.cgo2.c 20 | _cgo_defun.c 21 | _cgo_gotypes.go 22 | _cgo_export.* 23 | 24 | _testmain.go 25 | 26 | *.exe 27 | *.test 28 | *.prof 29 | 30 | .DS_Store 31 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: newreleases 2 | 3 | builds: 4 | - main: ./newreleases/main.go 5 | 6 | binary: newreleases 7 | 8 | flags: 9 | - -v 10 | - -trimpath 11 | 12 | ldflags: 13 | - -s -w -X newreleases.io/cmd.version={{.Version}} 14 | 15 | env: 16 | - CGO_ENABLED=0 17 | 18 | goos: 19 | - darwin 20 | - linux 21 | - windows 22 | 23 | goarch: 24 | - amd64 25 | - "386" 26 | - arm64 27 | - arm 28 | 29 | ignore: 30 | - goos: darwin 31 | goarch: "386" 32 | - goos: darwin 33 | goarch: arm 34 | - goos: windows 35 | goarch: arm64 36 | 37 | archives: 38 | - name_template: "{{ tolower .ProjectName }}-{{ tolower .Os }}-{{ tolower .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 39 | 40 | format: binary 41 | 42 | nfpms: 43 | - file_name_template: "{{ tolower .ProjectName }}-{{ tolower .Os }}-{{ tolower .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 44 | 45 | vendor: NewReleases 46 | homepage: https://newreleases.io/ 47 | 48 | maintainer: Janoš Guljaš 49 | 50 | description: NewReleases command line client. 51 | 52 | license: BSD 53 | 54 | formats: 55 | - deb 56 | - rpm 57 | 58 | bindir: /usr/bin 59 | 60 | brews: 61 | - 62 | name: newreleases 63 | 64 | commit_author: 65 | name: NewReleases Team 66 | email: support@newreleases.io 67 | 68 | caveats: | 69 | # Configuration 70 | 71 | This tool needs to authenticate to NewReleases API using a secret Auth Key 72 | that can be generated on the service settings web page 73 | https://newreleases.io/settings/api-keys. 74 | 75 | The key can be stored permanently by issuing interactive commands: 76 | 77 | newreleases configure 78 | 79 | or 80 | 81 | newreleases get-auth-key 82 | 83 | or it can be provided as the command line argument flag `--auth-key` on 84 | every newreleases command execution. 85 | 86 | # Usage 87 | 88 | Refer to the complete list of all commands on the project's README on 89 | https://github.com/newreleasesio/cli-go. 90 | 91 | homepage: "https://github.com/newreleasesio/cli-go" 92 | description: "A command line client for managing NewReleases projects." 93 | 94 | license: "BSD-3-Clause" 95 | 96 | test: | 97 | system "#{bin}/newreleases version" 98 | 99 | repository: 100 | owner: newreleasesio 101 | name: homebrew-cmd 102 | token: "{{ .Env.HOMEBREW_CMD_TAP_TOKEN }}" 103 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of NewReleases CLI authors for copyright purposes. 2 | 3 | Janoš Guljaš 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to follow. 4 | 5 | 1. Code should be `go fmt` formatted. 6 | 2. Exported types, constants, variables and functions should be documented. 7 | 3. Changes must be covered with tests. 8 | 4. All tests must pass constantly `go test .`. 9 | 10 | ## Versioning 11 | 12 | NewReleases CLI follows semantic versioning. New functionality should be accompanied by increment to the minor version number. 13 | 14 | ## Releasing 15 | 16 | Any code which is complete, tested, reviewed, and merged to master can be released. 17 | 18 | 1. Update the `version` number in `version.go`. 19 | 2. Make a pull request with these changes. 20 | 3. Once the pull request has been merged, visit [https://github.com/newreleasesio/cli-go/releases](https://github.com/newreleasesio/cli-go/release) and click `Draft a new release`. 21 | 4. Update the `Tag version` and `Release title` field with the new NewReleases CLI version. Be sure the version has a `v` prefixed in both places, e.g. `v1.25.0`. 22 | 5. Publish the release. 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of this project nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NewReleases 2 | 3 | [![Go](https://github.com/newreleasesio/cli-go/workflows/Go/badge.svg)](https://github.com/newreleasesio/cli-go/actions) 4 | [![NewReleases](https://newreleases.io/badge.svg)](https://newreleases.io/github/newreleasesio/cli-go) 5 | 6 | A command line client for managing [NewReleases](https://newreleases.io) projects. 7 | 8 | # Installation 9 | 10 | NewReleases client binaries have no external dependencies and can be just copied and executed locally. 11 | 12 | Binary downloads of the NewReleases client can be found on the [Releases page](https://github.com/newreleasesio/cli-go/releases/latest). 13 | 14 | To install on macOS: 15 | 16 | ```sh 17 | wget https://github.com/newreleasesio/cli-go/releases/latest/download/newreleases-darwin-amd64 -O /usr/local/bin/newreleases 18 | chmod +x /usr/local/bin/newreleases 19 | ``` 20 | 21 | You may need additional privileges to write to `/usr/local/bin`, but the file can be saved at any location that you want. 22 | 23 | Supported operating systems and architectures: 24 | 25 | - macOS ARM 64bit `darwin-arm64` 26 | - macOS 64bit `darwin-amd64` 27 | - Linux 64bit `linux-amd64` 28 | - Linux 32bit `linux-386` 29 | - Linux ARM 64bit `linux-arm64` 30 | - Linux ARM 32bit `linux-armv6` 31 | - Windows 64bit `windows-amd64` 32 | - Windows ARM 64bit `windows-arm64` 33 | - Windows 32bit `windows-386` 34 | 35 | Deb and RPM packages are also built. 36 | 37 | This tool is implemented using the Go programming language and can be also installed by issuing a `go get` command: 38 | 39 | ```sh 40 | go get -u newreleases.io/cmd/newreleases 41 | ``` 42 | 43 | ## Homebrew 44 | 45 | A convenience installation for macOS and Linux users. 46 | 47 | ```sh 48 | brew install newreleasesio/cmd/newreleases 49 | ``` 50 | 51 | # Configuration 52 | 53 | This tool needs to authenticate to NewReleases API using a secret Auth Key 54 | that can be generated on the service settings web pages. 55 | 56 | The key can be stored permanently by issuing interactive commands: 57 | 58 | ```sh 59 | newreleases configure 60 | ``` 61 | 62 | or 63 | 64 | ```sh 65 | newreleases get-auth-key 66 | ``` 67 | 68 | or it can be provided as the command line argument flag `--auth-key` on every newreleases command execution. 69 | 70 | # Usage 71 | 72 | ## Getting help 73 | 74 | NewReleases client and its commands have help pages associated with them that can be printed out with `-h` flag: 75 | 76 | ```sh 77 | newreleases -h 78 | newreleases get-auth-key -h 79 | newreleases project add -h 80 | ``` 81 | 82 | ## Working with projects 83 | 84 | The base command for getting projects is `project` and it shows available sub-commands which are `list`, `search`, `get`, `add`, `update` and `remove`. 85 | 86 | ### List projects 87 | 88 | Listing all added projects is paginated and a page can be specified with `--page` (short `-p`) flag: 89 | 90 | ```sh 91 | newreleases project list 92 | newreleases project list -p 2 93 | ``` 94 | 95 | Project can be filtered by provider: 96 | 97 | ```sh 98 | newreleases project list --provider github 99 | ``` 100 | 101 | and the order can be specified with `--order` flag which can have values `updated`, `added` or `name`: 102 | 103 | ```sh 104 | newreleases project list --order name 105 | ``` 106 | 107 | Projects can be searched by name with: 108 | 109 | ```sh 110 | newreleases project search go 111 | ``` 112 | 113 | where `go` is the example of a search string. 114 | 115 | ### Search projects 116 | 117 | Search results can be filtered by provider, just as listing can be with `--provider` flag: 118 | 119 | ```sh 120 | newreleases project search go --provider github 121 | ``` 122 | 123 | ### Get a project 124 | 125 | Information about a specific project can be retrieved with: 126 | 127 | ```sh 128 | newreleases project get github golang/go 129 | ``` 130 | 131 | or by a project id: 132 | 133 | ```sh 134 | newreleases project get mdsbe60td5gwgzetyksdfeyxt4 135 | ``` 136 | 137 | ### Add new project to track 138 | 139 | A project can be added with: 140 | 141 | ```sh 142 | newreleases project add github golang/go 143 | ``` 144 | 145 | But there is a number of options that can be set, as by default, none of the notifications are enabled. 146 | 147 | To enable emailing: 148 | 149 | ```sh 150 | newreleases project add github golang/go --email daily 151 | ``` 152 | 153 | Or to add Slack notifications as well, but exclude pre-releases: 154 | 155 | ```sh 156 | newreleases project add github golang/go --email daily --slack td5gwxt4mdsbe6gzetyksdfey0 --exclude-prereleases 157 | ``` 158 | 159 | More details about options can be found on `add` sub-command help page: 160 | 161 | ```sh 162 | newreleases project add -h 163 | ``` 164 | 165 | ### Update project options 166 | 167 | Updating a project options is also possible. It contains the same options as the `add` command with additional flags to remove some of them. More information about options can be found on `update` sub-command help page: 168 | 169 | ```sh 170 | newreleases project update -h 171 | ``` 172 | 173 | It is important that only specified options will be changed. For example, specifying different Slack channels will not remove already set other options like Telegram or Email or exclusions. 174 | 175 | ```sh 176 | newreleases project update github golang/go --slack td5gwxt4mdsbe6gzetyksdfey0 177 | ``` 178 | 179 | ### Remove a project 180 | 181 | To remove the project from tracking its releases: 182 | 183 | ```sh 184 | newreleases project remove github golang/go 185 | ``` 186 | 187 | or by a project id: 188 | 189 | ```sh 190 | newreleases project remove mdsbe60td5gwgzetyksdfeyxt4 191 | ``` 192 | 193 | ## Getting releases 194 | 195 | The base command for getting releases is `release` and it shows available sub-commands which are `list`, `get`, and `note`. 196 | 197 | ### List releases of a project 198 | 199 | To list all releases in chronological order of one project: 200 | 201 | ```sh 202 | newreleases release list github golang/go 203 | ``` 204 | 205 | where the first argument after `list` is the provider and the second one is the project name. 206 | 207 | or by project id: 208 | 209 | ```sh 210 | newreleases release list mdsbe60td5gwgzetyksdfeyxt4 211 | ``` 212 | 213 | where the only argument after `list` is the project ID. 214 | 215 | Results are paginated and the requested page can be specified with `--page` (short `-p`) flag. 216 | 217 | ```sh 218 | newreleases release list github golang/go -p 2 219 | ``` 220 | 221 | ### Get a release information 222 | 223 | To get information about only one release, there is the `get` sub-command: 224 | 225 | ```sh 226 | newreleases release get github golang/go go1.13.5 227 | ``` 228 | 229 | ```sh 230 | newreleases release get mdsbe60td5gwgzetyksdfeyxt4 go1.13.5 231 | ``` 232 | 233 | ### Get the latest non-excluded project release information 234 | 235 | To get information about only one release, there is the `get-latest` sub-command: 236 | 237 | ```sh 238 | newreleases release get-latest github golang/go 239 | ``` 240 | 241 | ```sh 242 | newreleases release get-latest mdsbe60td5gwgzetyksdfeyxt4 243 | ``` 244 | 245 | ### Get a release note 246 | 247 | To get a release note about a release, there is the `note` sub-command: 248 | 249 | ```sh 250 | newreleases release note npm vue 2.6.11 251 | ``` 252 | 253 | ```sh 254 | newreleases release note gzetyksdfeyxt4mdsbe60td5gw 2.6.11 255 | ``` 256 | 257 | ## Listing providers 258 | 259 | NewReleases supports a number of clients and they can be listed with: 260 | 261 | ```sh 262 | newreleases providers 263 | ``` 264 | 265 | To list only providers that you have project added from: 266 | 267 | ```sh 268 | newreleases providers --added 269 | ``` 270 | 271 | This information can be useful when filtering projects by a provider. 272 | 273 | ## Listing available notification channels 274 | 275 | Notification channels can be managed only over the service's web interface. With NewReleases CLI client, they can be listed to relate their IDs from the output from other commands with their names. Available commands: 276 | 277 | ```sh 278 | newreleases slack 279 | newreleases telegram 280 | newreleases discord 281 | newreleases hangouts-chat 282 | newreleases microsoft-teams 283 | newreleases mattermost 284 | newreleases rocketchat 285 | newreleases matrix 286 | newreleases webhook 287 | ``` 288 | 289 | ## Working with tags 290 | 291 | The base command for getting tags is `tag` and it shows available sub-commands which are `list`, `get`, `add`, `update` and `remove`. 292 | 293 | ### List tags 294 | 295 | Listing all tags: 296 | 297 | ```sh 298 | newreleases tag list 299 | ``` 300 | 301 | ### Get a tag 302 | 303 | Information about a specific tag can be retrieved with: 304 | 305 | ```sh 306 | newreleases tag get 33f1db7254b9 307 | ``` 308 | 309 | ### Add new tag 310 | 311 | A tag can be added with specifying its name: 312 | 313 | ```sh 314 | newreleases tag add Awesome 315 | ``` 316 | 317 | ### Update tag name 318 | 319 | To change a tag name, it should be referenced by its ID and a new name should be specified. 320 | 321 | ```sh 322 | newreleases tag update 33f1db7254b9 --name Cool 323 | ``` 324 | 325 | ### Remove a tag 326 | 327 | Tag is removed by its ID: 328 | 329 | ```sh 330 | newreleases tag remove 33f1db7254b9 331 | ``` 332 | 333 | # Versioning 334 | 335 | To see the current version of the binary, execute: 336 | 337 | ```sh 338 | newreleases version 339 | ``` 340 | 341 | Each version is tagged and the version is updated accordingly in `version.go` file. 342 | 343 | # Contributing 344 | 345 | We love pull requests! Please see the [contribution guidelines](CONTRIBUTING.md). 346 | 347 | # License 348 | 349 | This application is distributed under the BSD-style license found in the [LICENSE](LICENSE) file. 350 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Package cmd is the NewReleases CLI implementation. 7 | package cmd 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module newreleases.io/cmd 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/olekukonko/tablewriter v0.0.5 7 | github.com/spf13/cobra v1.9.1 8 | github.com/spf13/viper v1.19.0 9 | golang.org/x/term v0.29.0 10 | jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056 11 | newreleases.io/newreleases v1.10.0 12 | ) 13 | 14 | require ( 15 | github.com/fsnotify/fsnotify v1.7.0 // indirect 16 | github.com/hashicorp/hcl v1.0.0 // indirect 17 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 18 | github.com/magiconair/properties v1.8.7 // indirect 19 | github.com/mattn/go-runewidth v0.0.9 // indirect 20 | github.com/mitchellh/mapstructure v1.5.0 // indirect 21 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 22 | github.com/sagikazarmark/locafero v0.4.0 // indirect 23 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 24 | github.com/sourcegraph/conc v0.3.0 // indirect 25 | github.com/spf13/afero v1.11.0 // indirect 26 | github.com/spf13/cast v1.6.0 // indirect 27 | github.com/spf13/pflag v1.0.6 // indirect 28 | github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect 29 | github.com/subosito/gotenv v1.6.0 // indirect 30 | go.uber.org/atomic v1.9.0 // indirect 31 | go.uber.org/multierr v1.9.0 // indirect 32 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 33 | golang.org/x/net v0.23.0 // indirect 34 | golang.org/x/sys v0.30.0 // indirect 35 | golang.org/x/text v0.14.0 // indirect 36 | gopkg.in/ini.v1 v1.67.0 // indirect 37 | gopkg.in/yaml.v3 v3.0.1 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 5 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 7 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 8 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 9 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 10 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 11 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 12 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 13 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 14 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 15 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 16 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 17 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 18 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 19 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 20 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 21 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 22 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 23 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 24 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 25 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 26 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 27 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 28 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= 29 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 32 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 33 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 34 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 35 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 36 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= 37 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= 38 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= 39 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= 40 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 41 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 42 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= 43 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= 44 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= 45 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 46 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 47 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 48 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 49 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 50 | github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= 51 | github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= 52 | github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= 53 | github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= 54 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 55 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 56 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 57 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 58 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 59 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 60 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 61 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 62 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 63 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 64 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 65 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 66 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 67 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 68 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= 69 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 70 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= 71 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 72 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 73 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 74 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 75 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 76 | golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= 77 | golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 78 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 79 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 80 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 81 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 82 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 83 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 84 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 85 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 86 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 87 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 88 | jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056 h1:6YFJoB+0fUH6X3xU/G2tQqCYg+PkGtnZ5nMR5rpw72g= 89 | jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:OxvTsCwKosqQ1q7B+8FwXqg4rKZ/UG9dUW+g/VL2xH4= 90 | newreleases.io/newreleases v1.10.0 h1:ZkWO3z3NL/MyvidYGZ7WbDERoaRZLAfqbIETLq4AQR8= 91 | newreleases.io/newreleases v1.10.0/go.mod h1:IFoaLRTd4ZNVIEIZflkjRt+0Z4BOgivteiLGs9vPobw= 92 | -------------------------------------------------------------------------------- /newreleases/cmd/auth.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | "strings" 11 | 12 | "github.com/spf13/cobra" 13 | "newreleases.io/newreleases" 14 | ) 15 | 16 | func (c *command) initAuthCmd() (err error) { 17 | cmd := &cobra.Command{ 18 | Use: "auth", 19 | Short: "Get API authentication keys", 20 | RunE: func(cmd *cobra.Command, args []string) (err error) { 21 | ctx, cancel := newClientContext(c.config) 22 | defer cancel() 23 | 24 | keys, err := c.authService.List(ctx) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | if len(keys) == 0 { 30 | cmd.Println("No auth keys found.") 31 | return nil 32 | } 33 | 34 | printAuthKeysTable(cmd, keys) 35 | 36 | return nil 37 | }, 38 | PreRunE: func(cmd *cobra.Command, args []string) error { 39 | if err := addClientConfigOptions(cmd, c.config); err != nil { 40 | return err 41 | } 42 | return c.setAuthService(cmd, args) 43 | }, 44 | } 45 | 46 | c.root.AddCommand(cmd) 47 | return addClientFlags(cmd) 48 | } 49 | 50 | func (c *command) setAuthService(cmd *cobra.Command, args []string) (err error) { 51 | if c.authService != nil { 52 | return nil 53 | } 54 | client, err := c.getClient(cmd) 55 | if err != nil { 56 | return err 57 | } 58 | c.authService = client.Auth 59 | return nil 60 | } 61 | 62 | type authService interface { 63 | List(ctx context.Context) (keys []newreleases.AuthKey, err error) 64 | } 65 | 66 | func printAuthKeysTable(cmd *cobra.Command, keys []newreleases.AuthKey) { 67 | table := newTable(cmd.OutOrStdout()) 68 | table.SetHeader([]string{"Name", "Authorized Networks", "Secret"}) 69 | for _, key := range keys { 70 | var authorizedNetworks []string 71 | for _, an := range key.AuthorizedNetworks { 72 | authorizedNetworks = append(authorizedNetworks, an.String()) 73 | } 74 | table.Append([]string{key.Name, strings.Join(authorizedNetworks, ", "), key.Secret}) 75 | } 76 | table.Render() 77 | } 78 | -------------------------------------------------------------------------------- /newreleases/cmd/auth_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "context" 11 | "net" 12 | "testing" 13 | 14 | "newreleases.io/cmd/newreleases/cmd" 15 | "newreleases.io/newreleases" 16 | ) 17 | 18 | func TestAuthCmd(t *testing.T) { 19 | _, ipNet1, err := net.ParseCIDR("127.0.0.0/8") 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | _, ipNet2, err := net.ParseCIDR("123.33.44.1/32") 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | for _, tc := range []struct { 29 | name string 30 | authService cmd.AuthService 31 | wantOutput string 32 | wantError error 33 | }{ 34 | { 35 | name: "no keys", 36 | authService: newMockAuthService(nil, nil), 37 | wantOutput: "No auth keys found.\n", 38 | }, 39 | { 40 | name: "single key", 41 | authService: newMockAuthService([]newreleases.AuthKey{{Name: "Master", AuthorizedNetworks: []net.IPNet{*ipNet1}}}, nil), 42 | wantOutput: "NAME AUTHORIZED NETWORKS SECRET \nMaster 127.0.0.0/8 \n", 43 | }, 44 | { 45 | name: "two keys", 46 | authService: newMockAuthService([]newreleases.AuthKey{ 47 | {Name: "Master", AuthorizedNetworks: []net.IPNet{*ipNet1}}, 48 | {Name: "Another", AuthorizedNetworks: []net.IPNet{*ipNet1, *ipNet2}}, 49 | }, nil), 50 | wantOutput: "NAME AUTHORIZED NETWORKS SECRET \nMaster 127.0.0.0/8 \nAnother 127.0.0.0/8, 123.33.44.1/32 \n", 51 | }, 52 | { 53 | name: "error", 54 | authService: newMockAuthService(nil, errTest), 55 | wantError: errTest, 56 | }, 57 | } { 58 | t.Run(tc.name, func(t *testing.T) { 59 | var outputBuf bytes.Buffer 60 | if err := newCommand(t, 61 | cmd.WithArgs("auth"), 62 | cmd.WithOutput(&outputBuf), 63 | cmd.WithAuthService(tc.authService), 64 | ).Execute(); err != tc.wantError { 65 | t.Fatalf("got error %v, want %v", err, tc.wantError) 66 | } 67 | 68 | gotOutput := outputBuf.String() 69 | if gotOutput != tc.wantOutput { 70 | t.Errorf("got output %q, want %q", gotOutput, tc.wantOutput) 71 | } 72 | }) 73 | } 74 | } 75 | 76 | type mockAuthService struct { 77 | keys []newreleases.AuthKey 78 | err error 79 | } 80 | 81 | func newMockAuthService(keys []newreleases.AuthKey, err error) (s mockAuthService) { 82 | return mockAuthService{keys: keys, err: err} 83 | } 84 | 85 | func (s mockAuthService) List(ctx context.Context) (keys []newreleases.AuthKey, err error) { 86 | return s.keys, s.err 87 | } 88 | -------------------------------------------------------------------------------- /newreleases/cmd/client.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | "errors" 11 | "net/url" 12 | "time" 13 | 14 | "github.com/spf13/cobra" 15 | "github.com/spf13/viper" 16 | "newreleases.io/newreleases" 17 | ) 18 | 19 | func (c *command) getClient(cmd *cobra.Command) (client *newreleases.Client, err error) { 20 | if c.client != nil { 21 | return c.client, nil 22 | } 23 | 24 | authKey := c.config.GetString(optionNameAuthKey) 25 | if authKey == "" { 26 | cmd.Println(configurationHelp) 27 | cmd.Println() 28 | return nil, errors.New("auth key not configured") 29 | } 30 | o, err := newClientOptions(cmd) 31 | if err != nil { 32 | return nil, err 33 | } 34 | c.client = newreleases.NewClient(authKey, o) 35 | return c.client, nil 36 | } 37 | 38 | func newClientOptions(cmd *cobra.Command) (o *newreleases.ClientOptions, err error) { 39 | v, err := cmd.Flags().GetString(optionNameAPIEndpoint) 40 | if err != nil { 41 | return nil, err 42 | } 43 | var baseURL *url.URL 44 | if v != "" { 45 | baseURL, err = url.Parse(v) 46 | if err != nil { 47 | return nil, err 48 | } 49 | } 50 | return &newreleases.ClientOptions{BaseURL: baseURL}, nil 51 | } 52 | 53 | func newClientContext(config *viper.Viper) (ctx context.Context, cancel context.CancelFunc) { 54 | return context.WithTimeout(context.Background(), config.GetDuration(optionNameTimeout)) 55 | } 56 | 57 | func addClientFlags(cmd *cobra.Command) (err error) { 58 | flags := cmd.Flags() 59 | flags.String(optionNameAuthKey, "", "API auth key") 60 | flags.Duration(optionNameTimeout, 30*time.Second, "API request timeout") 61 | flags.String(optionNameAPIEndpoint, "", "API Endpoint") 62 | if err := flags.MarkHidden(optionNameAPIEndpoint); err != nil { 63 | return err 64 | } 65 | return nil 66 | } 67 | 68 | func addClientConfigOptions(cmd *cobra.Command, config *viper.Viper) (err error) { 69 | flags := cmd.Flags() 70 | if err := config.BindPFlag(optionNameAuthKey, flags.Lookup(optionNameAuthKey)); err != nil { 71 | return err 72 | } 73 | if err := config.BindPFlag(optionNameTimeout, flags.Lookup(optionNameTimeout)); err != nil { 74 | return err 75 | } 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /newreleases/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "errors" 10 | "io" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | 15 | "github.com/olekukonko/tablewriter" 16 | "github.com/spf13/cobra" 17 | "github.com/spf13/viper" 18 | "newreleases.io/newreleases" 19 | ) 20 | 21 | const ( 22 | optionNameAuthKey = "auth-key" 23 | optionNameTimeout = "timeout" 24 | optionNameAPIEndpoint = "api-endpoint" 25 | ) 26 | 27 | func init() { 28 | cobra.EnableCommandSorting = false 29 | } 30 | 31 | type command struct { 32 | root *cobra.Command 33 | config *viper.Viper 34 | client *newreleases.Client 35 | cfgFile string 36 | homeDir string 37 | passwordReader passwordReader 38 | authKeysGetter authKeysGetter 39 | authService authService 40 | projectsService projectsService 41 | releasesService releasesService 42 | providersService providersService 43 | slackChannelsService slackChannelsService 44 | telegramChatsService telegramChatsService 45 | discordChannelsService discordChannelsService 46 | hangoutsChatWebhooksService hangoutsChatWebhooksService 47 | microsoftTeamsWebhooksService microsoftTeamsWebhooksService 48 | mattermostWebhooksService mattermostWebhooksService 49 | rocketchatWebhooksService rocketchatWebhooksService 50 | matrixRoomsService matrixRoomsService 51 | webhooksService webhooksService 52 | tagsService tagsService 53 | } 54 | 55 | type option func(*command) 56 | 57 | func newCommand(opts ...option) (c *command, err error) { 58 | c = &command{ 59 | root: &cobra.Command{ 60 | Use: "newreleases", 61 | Short: "newreleases manages projects on NewReleases service", 62 | Long: `NewReleases is a release tracker for software engineers. 63 | 64 | More information at https://newreleases.io.`, 65 | SilenceErrors: true, 66 | SilenceUsage: true, 67 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 68 | cmdName := cmd.Name() 69 | return c.initConfig(cmdName != cmdNameConfigure && cmdName != cmdNameGetAuthKey) 70 | }, 71 | }, 72 | } 73 | 74 | for _, o := range opts { 75 | o(c) 76 | } 77 | if c.passwordReader == nil { 78 | c.passwordReader = new(stdInPasswordReader) 79 | } 80 | 81 | c.initGlobalFlags() 82 | 83 | if err := c.initProjectCmd(); err != nil { 84 | return nil, err 85 | } 86 | if err := c.initReleaseCmd(); err != nil { 87 | return nil, err 88 | } 89 | if err := c.initProviderCmd(); err != nil { 90 | return nil, err 91 | } 92 | 93 | if err := c.initSlackCmd(); err != nil { 94 | return nil, err 95 | } 96 | if err := c.initTelegramCmd(); err != nil { 97 | return nil, err 98 | } 99 | if err := c.initDiscordCmd(); err != nil { 100 | return nil, err 101 | } 102 | if err := c.initHangoutsChatCmd(); err != nil { 103 | return nil, err 104 | } 105 | if err := c.initMicrosoftTeamsCmd(); err != nil { 106 | return nil, err 107 | } 108 | if err := c.initMattermostCmd(); err != nil { 109 | return nil, err 110 | } 111 | if err := c.initRocketchatCmd(); err != nil { 112 | return nil, err 113 | } 114 | if err := c.initMatrixCmd(); err != nil { 115 | return nil, err 116 | } 117 | if err := c.initWebhookCmd(); err != nil { 118 | return nil, err 119 | } 120 | if err := c.initTagCmd(); err != nil { 121 | return nil, err 122 | } 123 | 124 | c.initConfigureCmd() 125 | if err := c.initGetAuthKeyCmd(); err != nil { 126 | return nil, err 127 | } 128 | if err := c.initAuthCmd(); err != nil { 129 | return nil, err 130 | } 131 | c.initVersionCmd() 132 | return c, nil 133 | } 134 | 135 | func (c *command) Execute() (err error) { 136 | return c.root.Execute() 137 | } 138 | 139 | // Execute parses command line arguments and runs appropriate functions. 140 | func Execute() error { 141 | c, err := newCommand() 142 | if err != nil { 143 | return err 144 | } 145 | return c.Execute() 146 | } 147 | 148 | func (c *command) initGlobalFlags() { 149 | globalFlags := c.root.PersistentFlags() 150 | globalFlags.StringVar(&c.cfgFile, "config", "", "config file (default is $HOME/.newreleases.yaml)") 151 | } 152 | 153 | func (c *command) initConfig(requireConfigFileIfSet bool) (err error) { 154 | config := viper.New() 155 | configName := ".newreleases" 156 | if c.cfgFile != "" { 157 | // Use config file from the flag. 158 | config.SetConfigFile(c.cfgFile) 159 | } else { 160 | // Find home directory. 161 | if err := c.setHomeDir(); err != nil { 162 | return err 163 | } 164 | // Search config in home directory with name ".newreleases" (without extension). 165 | config.AddConfigPath(c.homeDir) 166 | config.SetConfigName(configName) 167 | requireConfigFileIfSet = false 168 | } 169 | 170 | // Environment 171 | config.SetEnvPrefix("newreleases") 172 | config.AutomaticEnv() // read in environment variables that match 173 | config.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) 174 | 175 | if c.homeDir != "" && c.cfgFile == "" { 176 | c.cfgFile = filepath.Join(c.homeDir, configName+".yaml") 177 | } 178 | 179 | // If a config file is found, read it in. 180 | if err := config.ReadInConfig(); err != nil { 181 | if requireConfigFileIfSet { 182 | return err 183 | } 184 | var e viper.ConfigFileNotFoundError 185 | if !errors.As(err, &e) && !os.IsNotExist(err) { 186 | return err 187 | } 188 | } 189 | c.config = config 190 | return nil 191 | } 192 | 193 | func (c *command) setHomeDir() (err error) { 194 | if c.homeDir != "" { 195 | return 196 | } 197 | dir, err := os.UserHomeDir() 198 | if err != nil { 199 | return err 200 | } 201 | c.homeDir = dir 202 | return nil 203 | } 204 | 205 | func (c *command) writeConfig(cmd *cobra.Command, authKey string) (err error) { 206 | c.config.Set(optionNameAuthKey, strings.TrimSpace(authKey)) 207 | err = c.config.WriteConfig() 208 | if _, ok := err.(viper.ConfigFileNotFoundError); ok { 209 | err = c.config.SafeWriteConfigAs(c.cfgFile) 210 | } 211 | return err 212 | } 213 | 214 | func newTable(w io.Writer) (table *tablewriter.Table) { 215 | table = tablewriter.NewWriter(w) 216 | table.SetAutoWrapText(false) 217 | table.SetAutoFormatHeaders(true) 218 | table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) 219 | table.SetAlignment(tablewriter.ALIGN_LEFT) 220 | table.SetCenterSeparator("") 221 | table.SetColumnSeparator("") 222 | table.SetRowSeparator("") 223 | table.SetHeaderLine(false) 224 | table.SetBorder(false) 225 | table.SetTablePadding(" ") 226 | table.SetNoWhiteSpace(true) 227 | return table 228 | } 229 | 230 | func yesNo(b bool) (s string) { 231 | if b { 232 | return "yes" 233 | } 234 | return "no" 235 | } 236 | -------------------------------------------------------------------------------- /newreleases/cmd/cmd_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "os" 12 | "regexp" 13 | "testing" 14 | "time" 15 | 16 | "newreleases.io/cmd/newreleases/cmd" 17 | ) 18 | 19 | var homeDir string 20 | 21 | var errTest = errors.New("test error") 22 | 23 | func TestMain(m *testing.M) { 24 | dir, err := os.MkdirTemp("", "newreleases-cmd-") 25 | if err != nil { 26 | fmt.Fprintln(os.Stderr, err) 27 | os.Exit(1) 28 | } 29 | defer os.RemoveAll(dir) 30 | 31 | homeDir = dir 32 | 33 | os.Exit(m.Run()) 34 | } 35 | 36 | func newCommand(t *testing.T, opts ...cmd.Option) (c *cmd.Command) { 37 | t.Helper() 38 | 39 | c, err := cmd.NewCommand(append([]cmd.Option{cmd.WithHomeDir(homeDir)}, opts...)...) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | return c 44 | } 45 | 46 | func newTime(t *testing.T, s string) (tm time.Time) { 47 | t.Helper() 48 | 49 | tm, err := time.Parse(time.RFC3339Nano, s) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | return tm 54 | } 55 | 56 | var spaceRe = regexp.MustCompile(`\s+`) 57 | 58 | func trimSpace(s string) string { 59 | return spaceRe.ReplaceAllString(s, " ") 60 | } 61 | -------------------------------------------------------------------------------- /newreleases/cmd/configure.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "bufio" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | const cmdNameConfigure = "configure" 15 | 16 | func (c *command) initConfigureCmd() { 17 | c.root.AddCommand(&cobra.Command{ 18 | Use: cmdNameConfigure, 19 | Short: "Provide configuration values to be stored in a file", 20 | Long: configurationHelp, 21 | RunE: func(cmd *cobra.Command, args []string) (err error) { 22 | reader := bufio.NewReader(cmd.InOrStdin()) 23 | 24 | authKey, err := terminalPrompt(cmd, reader, "Auth Key") 25 | if err != nil { 26 | return err 27 | } 28 | if authKey == "" { 29 | cmd.PrintErr("No key provided.\n") 30 | cmd.Println("Configuration is not saved.") 31 | return nil 32 | } 33 | 34 | if err := c.writeConfig(cmd, authKey); err != nil { 35 | return err 36 | } 37 | 38 | cmd.Printf("Configuration saved to: %s.\n", c.cfgFile) 39 | return nil 40 | }, 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /newreleases/cmd/configure_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | "testing" 15 | 16 | "newreleases.io/cmd/newreleases/cmd" 17 | ) 18 | 19 | func TestConfigureCmd(t *testing.T) { 20 | for _, tc := range []struct { 21 | name string 22 | withConfigFlag bool 23 | newConfig bool 24 | authKey string 25 | wantOutputFunc func(filename string) string 26 | wantErrorOutput string 27 | wantData string 28 | }{ 29 | { 30 | name: "no key", 31 | wantOutputFunc: func(string) string { return "Auth Key: Configuration is not saved.\n" }, 32 | wantErrorOutput: "No key provided.\n", 33 | }, 34 | { 35 | name: "no key with config flag", 36 | withConfigFlag: true, 37 | wantOutputFunc: func(string) string { return "Auth Key: Configuration is not saved.\n" }, 38 | wantErrorOutput: "No key provided.\n", 39 | }, 40 | { 41 | name: "valid key", 42 | authKey: "z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71", 43 | wantOutputFunc: func(filename string) string { 44 | return fmt.Sprintf("Auth Key: Configuration saved to: %s.\n", filename) 45 | }, 46 | wantData: "auth-key: z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71\n", 47 | }, 48 | { 49 | name: "valid key with new config flag", 50 | withConfigFlag: true, 51 | newConfig: true, 52 | authKey: "9ty6an1z8jwn5ne0sg5a9b4qOpc6rpymcw71", 53 | wantOutputFunc: func(filename string) string { 54 | return fmt.Sprintf("Auth Key: Configuration saved to: %s.\n", filename) 55 | }, 56 | wantData: "auth-key: 9ty6an1z8jwn5ne0sg5a9b4qOpc6rpymcw71\n", 57 | }, 58 | { 59 | name: "valid key with existing config flag", 60 | withConfigFlag: true, 61 | authKey: "9ty6an1z8jwn5ne0sg5a9b4qOpc6rpymcw71", 62 | wantOutputFunc: func(filename string) string { 63 | return fmt.Sprintf("Auth Key: Configuration saved to: %s.\n", filename) 64 | }, 65 | wantData: "auth-key: 9ty6an1z8jwn5ne0sg5a9b4qOpc6rpymcw71\n", 66 | }, 67 | } { 68 | t.Run(tc.name, func(t *testing.T) { 69 | dir := t.TempDir() 70 | 71 | cfgFile := filepath.Join(dir, ".newreleases.yaml") 72 | if !tc.newConfig { 73 | f, err := os.Create(cfgFile) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | if err := f.Close(); err != nil { 78 | t.Fatal(err) 79 | } 80 | } 81 | 82 | args := []string{"configure"} 83 | var setCfgFile string 84 | if tc.withConfigFlag { 85 | args = append(args, "--config", cfgFile) 86 | } else { 87 | setCfgFile = cfgFile 88 | } 89 | 90 | var outputBuf, errorOutputBuf bytes.Buffer 91 | if err := newCommand(t, 92 | cmd.WithArgs(args...), 93 | cmd.WithOutput(&outputBuf), 94 | cmd.WithErrorOutput(&errorOutputBuf), 95 | cmd.WithInput(strings.NewReader(tc.authKey+"\n")), 96 | cmd.WithCfgFile(setCfgFile), 97 | cmd.WithHomeDir(dir), 98 | ).Execute(); err != nil { 99 | t.Fatal(err) 100 | } 101 | 102 | gotOutput := outputBuf.String() 103 | if wantOutput := tc.wantOutputFunc(cfgFile); wantOutput != "" { 104 | wantOutput := wantOutput 105 | if gotOutput != wantOutput { 106 | t.Errorf("got output %q, want %q", gotOutput, wantOutput) 107 | } 108 | } else { 109 | if gotOutput != "" { 110 | t.Errorf("got output %q, but it should not be", gotOutput) 111 | } 112 | } 113 | 114 | gotErrorOutput := errorOutputBuf.String() 115 | if gotErrorOutput != tc.wantErrorOutput { 116 | t.Errorf("got error output %q, want %q", gotErrorOutput, tc.wantErrorOutput) 117 | } 118 | 119 | if tc.wantData != "" { 120 | gotData, err := os.ReadFile(cfgFile) 121 | if err != nil { 122 | t.Fatal(err) 123 | } 124 | if string(gotData) != tc.wantData { 125 | t.Errorf("got config file data %q, want %q", string(gotData), tc.wantData) 126 | } 127 | } else { 128 | gotData, _ := os.ReadFile(cfgFile) 129 | if string(gotData) != "" { 130 | t.Errorf("got config file data %q, but it should not be", string(gotData)) 131 | } 132 | } 133 | }) 134 | } 135 | } 136 | 137 | func TestConfigureCmd_overwrite(t *testing.T) { 138 | dir := t.TempDir() 139 | 140 | cfgFile := filepath.Join(dir, ".newreleases.yaml") 141 | f, err := os.Create(cfgFile) 142 | if err != nil { 143 | t.Fatal(err) 144 | } 145 | if err := f.Close(); err != nil { 146 | t.Fatal(err) 147 | } 148 | 149 | testConfigre := func(t *testing.T, authKey string) { 150 | t.Helper() 151 | 152 | var outputBuf bytes.Buffer 153 | if err := newCommand(t, 154 | cmd.WithCfgFile(cfgFile), 155 | cmd.WithHomeDir(dir), 156 | cmd.WithArgs("configure"), 157 | cmd.WithOutput(&outputBuf), 158 | cmd.WithInput(strings.NewReader(authKey+"\n")), 159 | ).Execute(); err != nil { 160 | t.Fatal(err) 161 | } 162 | 163 | gotOutput := outputBuf.String() 164 | wantOutput := fmt.Sprintf("Auth Key: Configuration saved to: %s.\n", cfgFile) 165 | if gotOutput != wantOutput { 166 | t.Errorf("got output %q, want %q", gotOutput, wantOutput) 167 | } 168 | 169 | gotData, err := os.ReadFile(cfgFile) 170 | if err != nil { 171 | t.Fatal(err) 172 | } 173 | wantData := fmt.Sprintf("auth-key: %s\n", authKey) 174 | if string(gotData) != wantData { 175 | t.Errorf("got config file data %q, want %q", string(gotData), wantData) 176 | } 177 | } 178 | 179 | // save first key 180 | testConfigre(t, "z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71") 181 | // overwrite with the new key 182 | testConfigre(t, "9ty6an1z8jwn5ne0sg5a9b4qOpc6rpymcw71") 183 | } 184 | -------------------------------------------------------------------------------- /newreleases/cmd/discord.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initDiscordCmd() (err error) { 16 | cmd := &cobra.Command{ 17 | Use: "discord", 18 | Short: "List Discord integrations", 19 | RunE: func(cmd *cobra.Command, args []string) (err error) { 20 | ctx, cancel := newClientContext(c.config) 21 | defer cancel() 22 | 23 | channel, err := c.discordChannelsService.List(ctx) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | if len(channel) == 0 { 29 | cmd.Println("No Discord Channels found.") 30 | return nil 31 | } 32 | 33 | printDiscordChannelsTable(cmd, channel) 34 | 35 | return nil 36 | }, 37 | PreRunE: func(cmd *cobra.Command, args []string) error { 38 | if err := addClientConfigOptions(cmd, c.config); err != nil { 39 | return err 40 | } 41 | return c.setDiscordChannelsService(cmd, args) 42 | }, 43 | } 44 | 45 | c.root.AddCommand(cmd) 46 | return addClientFlags(cmd) 47 | } 48 | 49 | func (c *command) setDiscordChannelsService(cmd *cobra.Command, args []string) (err error) { 50 | if c.discordChannelsService != nil { 51 | return nil 52 | } 53 | client, err := c.getClient(cmd) 54 | if err != nil { 55 | return err 56 | } 57 | c.discordChannelsService = client.DiscordChannels 58 | return nil 59 | } 60 | 61 | type discordChannelsService interface { 62 | List(ctx context.Context) (channels []newreleases.DiscordChannel, err error) 63 | } 64 | 65 | func printDiscordChannelsTable(cmd *cobra.Command, channels []newreleases.DiscordChannel) { 66 | table := newTable(cmd.OutOrStdout()) 67 | table.SetHeader([]string{"ID", "Name"}) 68 | for _, e := range channels { 69 | table.Append([]string{e.ID, e.Name}) 70 | } 71 | table.Render() 72 | } 73 | -------------------------------------------------------------------------------- /newreleases/cmd/discord_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "context" 11 | "testing" 12 | 13 | "newreleases.io/cmd/newreleases/cmd" 14 | "newreleases.io/newreleases" 15 | ) 16 | 17 | func TestDiscordCmd(t *testing.T) { 18 | for _, tc := range []struct { 19 | name string 20 | discordChannelsService cmd.DiscordChannelsService 21 | wantOutput string 22 | wantError error 23 | }{ 24 | { 25 | name: "no channels", 26 | discordChannelsService: newMockDiscordChannelsService(nil, nil), 27 | wantOutput: "No Discord Channels found.\n", 28 | }, 29 | { 30 | name: "with channels", 31 | discordChannelsService: newMockDiscordChannelsService([]newreleases.DiscordChannel{ 32 | { 33 | ID: "4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1", 34 | Name: "NewReleases", 35 | }, 36 | { 37 | ID: "c6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1", 38 | Name: "Awesome project", 39 | }, 40 | }, nil), 41 | wantOutput: "ID NAME \n4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1 NewReleases \nc6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1 Awesome project \n", 42 | }, 43 | { 44 | name: "error", 45 | discordChannelsService: newMockDiscordChannelsService(nil, errTest), 46 | wantError: errTest, 47 | }, 48 | } { 49 | t.Run(tc.name, func(t *testing.T) { 50 | var outputBuf bytes.Buffer 51 | if err := newCommand(t, 52 | cmd.WithArgs("discord"), 53 | cmd.WithOutput(&outputBuf), 54 | cmd.WithDiscordChannelsService(tc.discordChannelsService), 55 | ).Execute(); err != tc.wantError { 56 | t.Fatalf("got error %v, want %v", err, tc.wantError) 57 | } 58 | 59 | gotOutput := outputBuf.String() 60 | if gotOutput != tc.wantOutput { 61 | t.Errorf("got output %q, want %q", gotOutput, tc.wantOutput) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | type mockDiscordChannelsService struct { 68 | channels []newreleases.DiscordChannel 69 | err error 70 | } 71 | 72 | func newMockDiscordChannelsService(channels []newreleases.DiscordChannel, err error) (s mockDiscordChannelsService) { 73 | return mockDiscordChannelsService{channels: channels, err: err} 74 | } 75 | 76 | func (s mockDiscordChannelsService) List(ctx context.Context) (channels []newreleases.DiscordChannel, err error) { 77 | return s.channels, s.err 78 | } 79 | -------------------------------------------------------------------------------- /newreleases/cmd/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import "io" 9 | 10 | type ( 11 | Command = command 12 | Option = option 13 | PasswordReader = passwordReader 14 | AuthKeysGetter = authKeysGetter 15 | AuthService = authService 16 | ProvidersService = providersService 17 | ProjectsService = projectsService 18 | ReleasesService = releasesService 19 | SlackChannelsService = slackChannelsService 20 | TelegramChatsService = telegramChatsService 21 | DiscordChannelsService = discordChannelsService 22 | HangoutsChatWebhooksService = hangoutsChatWebhooksService 23 | MicrosoftTeamsWebhooksService = microsoftTeamsWebhooksService 24 | MattermostWebhooksService = mattermostWebhooksService 25 | RocketchatWebhooksService = rocketchatWebhooksService 26 | MatrixRoomsService = matrixRoomsService 27 | WebhooksService = webhooksService 28 | TagsService = tagsService 29 | ) 30 | 31 | var ( 32 | NewCommand = newCommand 33 | ) 34 | 35 | func WithCfgFile(f string) func(c *Command) { 36 | return func(c *Command) { 37 | c.cfgFile = f 38 | } 39 | } 40 | 41 | func WithHomeDir(dir string) func(c *Command) { 42 | return func(c *Command) { 43 | c.homeDir = dir 44 | } 45 | } 46 | 47 | func WithArgs(a ...string) func(c *Command) { 48 | return func(c *Command) { 49 | c.root.SetArgs(a) 50 | } 51 | } 52 | 53 | func WithInput(r io.Reader) func(c *Command) { 54 | return func(c *Command) { 55 | c.root.SetIn(r) 56 | } 57 | } 58 | 59 | func WithOutput(w io.Writer) func(c *Command) { 60 | return func(c *Command) { 61 | c.root.SetOut(w) 62 | } 63 | } 64 | 65 | func WithErrorOutput(w io.Writer) func(c *Command) { 66 | return func(c *Command) { 67 | c.root.SetErr(w) 68 | } 69 | } 70 | 71 | func WithPasswordReader(r PasswordReader) func(c *Command) { 72 | return func(c *Command) { 73 | c.passwordReader = r 74 | } 75 | } 76 | 77 | func WithAuthKeysGetter(g AuthKeysGetter) func(c *Command) { 78 | return func(c *Command) { 79 | c.authKeysGetter = g 80 | } 81 | } 82 | 83 | func WithAuthService(s AuthService) func(c *Command) { 84 | return func(c *Command) { 85 | c.authService = s 86 | } 87 | } 88 | 89 | func WithProvidersService(s ProvidersService) func(c *Command) { 90 | return func(c *Command) { 91 | c.providersService = s 92 | } 93 | } 94 | 95 | func WithProjectsService(s ProjectsService) func(c *Command) { 96 | return func(c *Command) { 97 | c.projectsService = s 98 | } 99 | } 100 | 101 | func WithReleasesService(s ReleasesService) func(c *Command) { 102 | return func(c *Command) { 103 | c.releasesService = s 104 | } 105 | } 106 | 107 | func WithSlackChannelsService(s SlackChannelsService) func(c *Command) { 108 | return func(c *Command) { 109 | c.slackChannelsService = s 110 | } 111 | } 112 | 113 | func WithTelegramChatsService(s TelegramChatsService) func(c *Command) { 114 | return func(c *Command) { 115 | c.telegramChatsService = s 116 | } 117 | } 118 | 119 | func WithDiscordChannelsService(s DiscordChannelsService) func(c *Command) { 120 | return func(c *Command) { 121 | c.discordChannelsService = s 122 | } 123 | } 124 | 125 | func WithHangoutsChatWebhooksService(s HangoutsChatWebhooksService) func(c *Command) { 126 | return func(c *Command) { 127 | c.hangoutsChatWebhooksService = s 128 | } 129 | } 130 | 131 | func WithMicrosoftTeamsWebhooksService(s MicrosoftTeamsWebhooksService) func(c *Command) { 132 | return func(c *Command) { 133 | c.microsoftTeamsWebhooksService = s 134 | } 135 | } 136 | 137 | func WithMattermostWebhooksService(s MattermostWebhooksService) func(c *Command) { 138 | return func(c *Command) { 139 | c.mattermostWebhooksService = s 140 | } 141 | } 142 | 143 | func WithRocketchatWebhooksService(s RocketchatWebhooksService) func(c *Command) { 144 | return func(c *Command) { 145 | c.rocketchatWebhooksService = s 146 | } 147 | } 148 | 149 | func WithMatrixRoomsService(s MatrixRoomsService) func(c *Command) { 150 | return func(c *Command) { 151 | c.matrixRoomsService = s 152 | } 153 | } 154 | 155 | func WithWebhooksService(s WebhooksService) func(c *Command) { 156 | return func(c *Command) { 157 | c.webhooksService = s 158 | } 159 | } 160 | 161 | func WithTagsService(s TagsService) func(c *Command) { 162 | return func(c *Command) { 163 | c.tagsService = s 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /newreleases/cmd/get_auth_key.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "bufio" 10 | "context" 11 | "io" 12 | "strconv" 13 | "strings" 14 | 15 | "newreleases.io/newreleases" 16 | 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | const cmdNameGetAuthKey = "get-auth-key" 21 | 22 | func (c *command) initGetAuthKeyCmd() (err error) { 23 | getAuthKeyCmd := &cobra.Command{ 24 | Use: cmdNameGetAuthKey, 25 | Short: "Get API auth key and store it in the configuration", 26 | Long: configurationHelp, 27 | RunE: func(cmd *cobra.Command, args []string) (err error) { 28 | cmd.Println("Sign in to NewReleases with your credentials") 29 | cmd.Println("to get available API keys and store them in local configuration file.") 30 | 31 | reader := bufio.NewReader(cmd.InOrStdin()) 32 | 33 | email, err := terminalPrompt(cmd, reader, "Email") 34 | if err != nil { 35 | return err 36 | } 37 | password, err := terminalPromptPassword(cmd, c.passwordReader, "Password") 38 | if err != nil { 39 | return err 40 | } 41 | 42 | ctx, cancel := newClientContext(c.config) 43 | defer cancel() 44 | 45 | o, err := newClientOptions(cmd) 46 | if err != nil { 47 | return err 48 | } 49 | keys, err := c.authKeysGetter.GetAuthKeys(ctx, email, password, o) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | count := len(keys) 55 | if count == 0 { 56 | cmd.PrintErr("No auth keys found.\n") 57 | cmd.Println("Go to https://newreleases.io and create an auth key.") 58 | return nil 59 | } 60 | 61 | var selection int 62 | if count > 1 { 63 | 64 | cmd.Println() 65 | printAuthKeysTableSafe(cmd, keys) 66 | cmd.Println() 67 | 68 | for { 69 | in, err := terminalPrompt(cmd, reader, "Select auth key (enter row number)") 70 | if err != nil && err != io.EOF { 71 | return err 72 | } 73 | if in == "" { 74 | cmd.PrintErr("No key selected.\n") 75 | cmd.Println("Configuration is not saved.") 76 | return nil 77 | } 78 | 79 | i, err := strconv.Atoi(in) 80 | if err != nil || i <= 0 || i > count { 81 | cmd.PrintErr("Invalid row number.\n") 82 | continue 83 | } 84 | selection = i - 1 85 | break 86 | } 87 | } 88 | 89 | key := keys[selection] 90 | if err := c.writeConfig(cmd, key.Secret); err != nil { 91 | return err 92 | } 93 | cmd.Printf("Using auth key: %s.\n", key.Name) 94 | 95 | cmd.Printf("Configuration saved to: %s.\n", c.cfgFile) 96 | return nil 97 | }, 98 | PreRunE: func(cmd *cobra.Command, args []string) error { 99 | if err := addClientConfigOptions(cmd, c.config); err != nil { 100 | return err 101 | } 102 | return c.setAuthKeysGetter(cmd, args) 103 | }, 104 | } 105 | 106 | c.root.AddCommand(getAuthKeyCmd) 107 | return addClientFlags(getAuthKeyCmd) 108 | } 109 | 110 | func (c *command) setAuthKeysGetter(cmd *cobra.Command, args []string) (err error) { 111 | if c.authKeysGetter != nil { 112 | return nil 113 | } 114 | c.authKeysGetter = authKeysGetterFunc(newreleases.GetAuthKeys) 115 | return nil 116 | } 117 | 118 | type authKeysGetter interface { 119 | GetAuthKeys(ctx context.Context, email, password string, o *newreleases.ClientOptions) (keys []newreleases.AuthKey, err error) 120 | } 121 | 122 | type authKeysGetterFunc func(ctx context.Context, email, password string, o *newreleases.ClientOptions) (keys []newreleases.AuthKey, err error) 123 | 124 | func (f authKeysGetterFunc) GetAuthKeys(ctx context.Context, email, password string, o *newreleases.ClientOptions) (keys []newreleases.AuthKey, err error) { 125 | return f(ctx, email, password, o) 126 | } 127 | 128 | func printAuthKeysTableSafe(cmd *cobra.Command, keys []newreleases.AuthKey) { 129 | table := newTable(cmd.OutOrStdout()) 130 | table.SetHeader([]string{"", "Name", "Authorized Networks"}) 131 | for i, key := range keys { 132 | var authorizedNetworks []string 133 | for _, an := range key.AuthorizedNetworks { 134 | authorizedNetworks = append(authorizedNetworks, an.String()) 135 | } 136 | table.Append([]string{strconv.Itoa(i + 1), key.Name, strings.Join(authorizedNetworks, ", ")}) 137 | } 138 | table.Render() 139 | } 140 | -------------------------------------------------------------------------------- /newreleases/cmd/get_auth_key_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "context" 11 | "fmt" 12 | "os" 13 | "path/filepath" 14 | "strings" 15 | "testing" 16 | 17 | "newreleases.io/cmd/newreleases/cmd" 18 | "newreleases.io/newreleases" 19 | ) 20 | 21 | func TestGetAuthKeyCmd(t *testing.T) { 22 | for _, tc := range []struct { 23 | name string 24 | withConfigFlag bool 25 | newConfig bool 26 | input string 27 | authKeysGetter cmd.AuthKeysGetter 28 | wantOutputFunc func(filename string) string 29 | wantErrorOutput string 30 | wantData string 31 | wantError error 32 | }{ 33 | { 34 | name: "empty input", 35 | input: "\n", 36 | authKeysGetter: newMockAuthKeysGetter("", "myPassword", nil, nil), 37 | wantOutputFunc: func(string) string { 38 | return "Sign in to NewReleases with your credentials\nto get available API keys and store them in local configuration file.\nEmail: Password: \nGo to https://newreleases.io and create an auth key.\n" 39 | }, 40 | wantErrorOutput: "No auth keys found.\n", 41 | }, 42 | { 43 | name: "unauthorized", 44 | input: "me@newreleases.io\n", 45 | authKeysGetter: newMockAuthKeysGetter("me@newreleases.io", "wrongPassword", nil, nil), 46 | wantOutputFunc: func(string) string { 47 | return "Sign in to NewReleases with your credentials\nto get available API keys and store them in local configuration file.\nEmail: Password: \n" 48 | }, 49 | wantError: newreleases.ErrUnauthorized, 50 | }, 51 | { 52 | name: "single key", 53 | input: "me@newreleases.io\n", 54 | authKeysGetter: newMockAuthKeysGetter("me@newreleases.io", "myPassword", []newreleases.AuthKey{{Name: "Master", Secret: "z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71"}}, nil), 55 | wantOutputFunc: func(filename string) string { 56 | return fmt.Sprintf("Sign in to NewReleases with your credentials\nto get available API keys and store them in local configuration file.\nEmail: Password: \nUsing auth key: Master.\nConfiguration saved to: %s.\n", filename) 57 | }, 58 | wantData: "auth-key: z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71\ntimeout: 30s\n", 59 | }, 60 | { 61 | name: "single key with new config flag", 62 | withConfigFlag: true, 63 | newConfig: true, 64 | input: "me@newreleases.io\n", 65 | authKeysGetter: newMockAuthKeysGetter("me@newreleases.io", "myPassword", []newreleases.AuthKey{{Name: "Master", Secret: "z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71"}}, nil), 66 | wantOutputFunc: func(filename string) string { 67 | return fmt.Sprintf("Sign in to NewReleases with your credentials\nto get available API keys and store them in local configuration file.\nEmail: Password: \nUsing auth key: Master.\nConfiguration saved to: %s.\n", filename) 68 | }, 69 | wantData: "auth-key: z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71\ntimeout: 30s\n", 70 | }, 71 | { 72 | name: "single key with existing config flag", 73 | withConfigFlag: true, 74 | input: "me@newreleases.io\n", 75 | authKeysGetter: newMockAuthKeysGetter("me@newreleases.io", "myPassword", []newreleases.AuthKey{{Name: "Master", Secret: "z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71"}}, nil), 76 | wantOutputFunc: func(filename string) string { 77 | return fmt.Sprintf("Sign in to NewReleases with your credentials\nto get available API keys and store them in local configuration file.\nEmail: Password: \nUsing auth key: Master.\nConfiguration saved to: %s.\n", filename) 78 | }, 79 | wantData: "auth-key: z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71\ntimeout: 30s\n", 80 | }, 81 | { 82 | name: "multiple keys select first", 83 | input: "me@newreleases.io\n1\n", 84 | authKeysGetter: newMockAuthKeysGetter("me@newreleases.io", "myPassword", []newreleases.AuthKey{ 85 | {Name: "Master", Secret: "z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71"}, 86 | {Name: "Secondary", Secret: "ne0sg5a9b4qOpc9ty6az8jwn5n16rpymcw71"}, 87 | }, nil), 88 | wantOutputFunc: func(filename string) string { 89 | return fmt.Sprintf("Sign in to NewReleases with your credentials\nto get available API keys and store them in local configuration file.\nEmail: Password: \n\n NAME AUTHORIZED NETWORKS \n1 Master \n2 Secondary \n\nSelect auth key (enter row number): Using auth key: Master.\nConfiguration saved to: %s.\n", filename) 90 | }, 91 | wantData: "auth-key: z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71\ntimeout: 30s\n", 92 | }, 93 | { 94 | name: "multiple keys select second", 95 | input: "me@newreleases.io\n2\n", 96 | authKeysGetter: newMockAuthKeysGetter("me@newreleases.io", "myPassword", []newreleases.AuthKey{ 97 | {Name: "Master", Secret: "z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71"}, 98 | {Name: "Secondary", Secret: "ne0sg5a9b4qOpc9ty6az8jwn5n16rpymcw71"}, 99 | }, nil), 100 | wantOutputFunc: func(filename string) string { 101 | return fmt.Sprintf("Sign in to NewReleases with your credentials\nto get available API keys and store them in local configuration file.\nEmail: Password: \n\n NAME AUTHORIZED NETWORKS \n1 Master \n2 Secondary \n\nSelect auth key (enter row number): Using auth key: Secondary.\nConfiguration saved to: %s.\n", filename) 102 | }, 103 | wantData: "auth-key: ne0sg5a9b4qOpc9ty6az8jwn5n16rpymcw71\ntimeout: 30s\n", 104 | }, 105 | { 106 | name: "multiple keys select second with config flag", 107 | withConfigFlag: true, 108 | input: "me@newreleases.io\n2\n", 109 | authKeysGetter: newMockAuthKeysGetter("me@newreleases.io", "myPassword", []newreleases.AuthKey{ 110 | {Name: "Master", Secret: "z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71"}, 111 | {Name: "Secondary", Secret: "ne0sg5a9b4qOpc9ty6az8jwn5n16rpymcw71"}, 112 | }, nil), 113 | wantOutputFunc: func(filename string) string { 114 | return fmt.Sprintf("Sign in to NewReleases with your credentials\nto get available API keys and store them in local configuration file.\nEmail: Password: \n\n NAME AUTHORIZED NETWORKS \n1 Master \n2 Secondary \n\nSelect auth key (enter row number): Using auth key: Secondary.\nConfiguration saved to: %s.\n", filename) 115 | }, 116 | wantData: "auth-key: ne0sg5a9b4qOpc9ty6az8jwn5n16rpymcw71\ntimeout: 30s\n", 117 | }, 118 | { 119 | name: "multiple keys select none", 120 | input: "me@newreleases.io\n\n", 121 | authKeysGetter: newMockAuthKeysGetter("me@newreleases.io", "myPassword", []newreleases.AuthKey{ 122 | {Name: "Master", Secret: "z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71"}, 123 | {Name: "Secondary", Secret: "ne0sg5a9b4qOpc9ty6az8jwn5n16rpymcw71"}, 124 | }, nil), 125 | wantOutputFunc: func(string) string { 126 | return "Sign in to NewReleases with your credentials\nto get available API keys and store them in local configuration file.\nEmail: Password: \n\n NAME AUTHORIZED NETWORKS \n1 Master \n2 Secondary \n\nSelect auth key (enter row number): Configuration is not saved.\n" 127 | }, 128 | wantErrorOutput: "No key selected.\n", 129 | }, 130 | { 131 | name: "multiple keys select invalid then first", 132 | input: "me@newreleases.io\naaa\n1\n", 133 | authKeysGetter: newMockAuthKeysGetter("me@newreleases.io", "myPassword", []newreleases.AuthKey{ 134 | {Name: "Master", Secret: "z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71"}, 135 | {Name: "Secondary", Secret: "ne0sg5a9b4qOpc9ty6az8jwn5n16rpymcw71"}, 136 | }, nil), 137 | wantOutputFunc: func(filename string) string { 138 | return fmt.Sprintf("Sign in to NewReleases with your credentials\nto get available API keys and store them in local configuration file.\nEmail: Password: \n\n NAME AUTHORIZED NETWORKS \n1 Master \n2 Secondary \n\nSelect auth key (enter row number): Select auth key (enter row number): Using auth key: Master.\nConfiguration saved to: %s.\n", filename) 139 | }, 140 | wantErrorOutput: "Invalid row number.\n", 141 | wantData: "auth-key: z8jwn5ne0sg5a9b4qOpc9ty6an16rpymcw71\ntimeout: 30s\n", 142 | }, 143 | } { 144 | t.Run(tc.name, func(t *testing.T) { 145 | dir := t.TempDir() 146 | 147 | cfgFile := filepath.Join(dir, ".newreleases.yaml") 148 | if !tc.newConfig { 149 | f, err := os.Create(cfgFile) 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | if err := f.Close(); err != nil { 154 | t.Fatal(err) 155 | } 156 | } 157 | 158 | args := []string{"get-auth-key"} 159 | var setCfgFile string 160 | if tc.withConfigFlag { 161 | args = append(args, "--config", cfgFile) 162 | } else { 163 | setCfgFile = cfgFile 164 | } 165 | 166 | var outputBuf, errorOutputBuf bytes.Buffer 167 | if err := newCommand(t, 168 | cmd.WithCfgFile(setCfgFile), 169 | cmd.WithHomeDir(dir), 170 | cmd.WithArgs(args...), 171 | cmd.WithOutput(&outputBuf), 172 | cmd.WithErrorOutput(&errorOutputBuf), 173 | cmd.WithInput(strings.NewReader(tc.input)), 174 | cmd.WithPasswordReader(newMockPasswordReader("myPassword", nil)), 175 | cmd.WithAuthKeysGetter(tc.authKeysGetter), 176 | ).Execute(); err != tc.wantError { 177 | t.Fatalf("got error %v, want %v", err, tc.wantError) 178 | } 179 | 180 | gotOutput := outputBuf.String() 181 | if wantOutput := tc.wantOutputFunc(cfgFile); wantOutput != "" { 182 | wantOutput := wantOutput 183 | if gotOutput != wantOutput { 184 | t.Errorf("got output %q, want %q", gotOutput, wantOutput) 185 | } 186 | } else { 187 | if gotOutput != "" { 188 | t.Errorf("got output %q, but it should not be", gotOutput) 189 | } 190 | } 191 | 192 | gotErrorOutput := errorOutputBuf.String() 193 | if gotErrorOutput != tc.wantErrorOutput { 194 | t.Errorf("got error output %q, want %q", gotErrorOutput, tc.wantErrorOutput) 195 | } 196 | 197 | if tc.wantData != "" { 198 | gotData, err := os.ReadFile(cfgFile) 199 | if err != nil { 200 | t.Fatal(err) 201 | } 202 | if string(gotData) != tc.wantData { 203 | t.Errorf("got config file data %q, want %q", string(gotData), tc.wantData) 204 | } 205 | } else { 206 | gotData, _ := os.ReadFile(cfgFile) 207 | if string(gotData) != "" { 208 | t.Errorf("got config file data %q, but it should not be", string(gotData)) 209 | } 210 | } 211 | }) 212 | } 213 | } 214 | 215 | type mockPasswordReader struct { 216 | password string 217 | err error 218 | } 219 | 220 | func newMockPasswordReader(password string, err error) (r mockPasswordReader) { 221 | return mockPasswordReader{ 222 | password: password, 223 | err: err, 224 | } 225 | } 226 | 227 | func (r mockPasswordReader) ReadPassword() (password string, err error) { 228 | return r.password, r.err 229 | } 230 | 231 | type mockAuthKeysGetter struct { 232 | email string 233 | password string 234 | keys []newreleases.AuthKey 235 | err error 236 | } 237 | 238 | func newMockAuthKeysGetter(email, password string, keys []newreleases.AuthKey, err error) (g mockAuthKeysGetter) { 239 | return mockAuthKeysGetter{ 240 | email: email, 241 | password: password, 242 | keys: keys, 243 | err: err, 244 | } 245 | } 246 | 247 | func (g mockAuthKeysGetter) GetAuthKeys(_ context.Context, email, password string, _ *newreleases.ClientOptions) (keys []newreleases.AuthKey, err error) { 248 | if email != g.email || password != g.password { 249 | return nil, newreleases.ErrUnauthorized 250 | } 251 | return g.keys, g.err 252 | } 253 | -------------------------------------------------------------------------------- /newreleases/cmd/hangouts_chat.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initHangoutsChatCmd() (err error) { 16 | cmd := &cobra.Command{ 17 | Use: "hangouts-chat", 18 | Short: "List Hangouts Chat integrations", 19 | RunE: func(cmd *cobra.Command, args []string) (err error) { 20 | ctx, cancel := newClientContext(c.config) 21 | defer cancel() 22 | 23 | webhooks, err := c.hangoutsChatWebhooksService.List(ctx) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | if len(webhooks) == 0 { 29 | cmd.Println("No Hangouts Chat Webhooks found.") 30 | return nil 31 | } 32 | 33 | printWebhooksTable(cmd, webhooks) 34 | 35 | return nil 36 | }, 37 | PreRunE: func(cmd *cobra.Command, args []string) error { 38 | if err := addClientConfigOptions(cmd, c.config); err != nil { 39 | return err 40 | } 41 | return c.setHangoutsChatWebhooksService(cmd, args) 42 | }, 43 | } 44 | 45 | c.root.AddCommand(cmd) 46 | return addClientFlags(cmd) 47 | } 48 | 49 | func (c *command) setHangoutsChatWebhooksService(cmd *cobra.Command, args []string) (err error) { 50 | if c.hangoutsChatWebhooksService != nil { 51 | return nil 52 | } 53 | client, err := c.getClient(cmd) 54 | if err != nil { 55 | return err 56 | } 57 | c.hangoutsChatWebhooksService = client.HangoutsChatWebhooks 58 | return nil 59 | } 60 | 61 | type hangoutsChatWebhooksService interface { 62 | List(ctx context.Context) (webhooks []newreleases.Webhook, err error) 63 | } 64 | -------------------------------------------------------------------------------- /newreleases/cmd/hangouts_chat_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "context" 11 | "testing" 12 | 13 | "newreleases.io/cmd/newreleases/cmd" 14 | "newreleases.io/newreleases" 15 | ) 16 | 17 | func TestHangoutsChatCmd(t *testing.T) { 18 | for _, tc := range []struct { 19 | name string 20 | hangoutsChatWebhooksService cmd.HangoutsChatWebhooksService 21 | wantOutput string 22 | wantError error 23 | }{ 24 | { 25 | name: "no webhooks", 26 | hangoutsChatWebhooksService: newMockHangoutsChatWebhooksService(nil, nil), 27 | wantOutput: "No Hangouts Chat Webhooks found.\n", 28 | }, 29 | { 30 | name: "with webhooks", 31 | hangoutsChatWebhooksService: newMockHangoutsChatWebhooksService([]newreleases.Webhook{ 32 | { 33 | ID: "4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1", 34 | Name: "NewReleases", 35 | }, 36 | { 37 | ID: "c6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1", 38 | Name: "Awesome project", 39 | }, 40 | }, nil), 41 | wantOutput: "ID NAME \n4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1 NewReleases \nc6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1 Awesome project \n", 42 | }, 43 | { 44 | name: "error", 45 | hangoutsChatWebhooksService: newMockHangoutsChatWebhooksService(nil, errTest), 46 | wantError: errTest, 47 | }, 48 | } { 49 | t.Run(tc.name, func(t *testing.T) { 50 | var outputBuf bytes.Buffer 51 | if err := newCommand(t, 52 | cmd.WithArgs("hangouts-chat"), 53 | cmd.WithOutput(&outputBuf), 54 | cmd.WithHangoutsChatWebhooksService(tc.hangoutsChatWebhooksService), 55 | ).Execute(); err != tc.wantError { 56 | t.Fatalf("got error %v, want %v", err, tc.wantError) 57 | } 58 | 59 | gotOutput := outputBuf.String() 60 | if gotOutput != tc.wantOutput { 61 | t.Errorf("got output %q, want %q", gotOutput, tc.wantOutput) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | type mockHangoutsChatWebhooksService struct { 68 | webhooks []newreleases.Webhook 69 | err error 70 | } 71 | 72 | func newMockHangoutsChatWebhooksService(webhooks []newreleases.Webhook, err error) (s mockHangoutsChatWebhooksService) { 73 | return mockHangoutsChatWebhooksService{webhooks: webhooks, err: err} 74 | } 75 | 76 | func (s mockHangoutsChatWebhooksService) List(ctx context.Context) (webhooks []newreleases.Webhook, err error) { 77 | return s.webhooks, s.err 78 | } 79 | -------------------------------------------------------------------------------- /newreleases/cmd/help.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. { 5 | 6 | package cmd 7 | 8 | var configurationHelp = `Initial configuration: 9 | 10 | This tool needs to authenticate to NewReleases API using a secret Auth Key 11 | that can be generated on the service settings web pages. 12 | 13 | The key can be stored permanently by issuing interactive commands: 14 | 15 | newreleases configure 16 | 17 | or 18 | 19 | newreleases get-auth-key 20 | 21 | or it can be provided as the command line argument flag --auth-key on every 22 | newreleases tool execution.` 23 | -------------------------------------------------------------------------------- /newreleases/cmd/help_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "strings" 11 | "testing" 12 | 13 | "newreleases.io/cmd/newreleases/cmd" 14 | ) 15 | 16 | func TestRootCmdHelp(t *testing.T) { 17 | for _, arg := range []string{ 18 | "", 19 | "-h", 20 | "--help", 21 | } { 22 | var outputBuf bytes.Buffer 23 | if err := newCommand(t, 24 | cmd.WithArgs(arg), 25 | cmd.WithOutput(&outputBuf), 26 | ).Execute(); err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | want := "NewReleases is a release tracker for software engineers" 31 | got := outputBuf.String() 32 | if !strings.Contains(got, want) { 33 | t.Errorf("output %q for arg %q, does not contain %q", got, arg, want) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /newreleases/cmd/matrix.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initMatrixCmd() (err error) { 16 | cmd := &cobra.Command{ 17 | Use: "matrix", 18 | Short: "List Matrix integrations", 19 | RunE: func(cmd *cobra.Command, args []string) (err error) { 20 | ctx, cancel := newClientContext(c.config) 21 | defer cancel() 22 | 23 | rooms, err := c.matrixRoomsService.List(ctx) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | if len(rooms) == 0 { 29 | cmd.Println("No Matrix Rooms found.") 30 | return nil 31 | } 32 | 33 | printMatrixRoomsTable(cmd, rooms) 34 | 35 | return nil 36 | }, 37 | PreRunE: func(cmd *cobra.Command, args []string) error { 38 | if err := addClientConfigOptions(cmd, c.config); err != nil { 39 | return err 40 | } 41 | return c.setMatrixRoomsService(cmd, args) 42 | }, 43 | } 44 | 45 | c.root.AddCommand(cmd) 46 | return addClientFlags(cmd) 47 | } 48 | 49 | func (c *command) setMatrixRoomsService(cmd *cobra.Command, args []string) (err error) { 50 | if c.matrixRoomsService != nil { 51 | return nil 52 | } 53 | client, err := c.getClient(cmd) 54 | if err != nil { 55 | return err 56 | } 57 | c.matrixRoomsService = client.MatrixRooms 58 | return nil 59 | } 60 | 61 | type matrixRoomsService interface { 62 | List(ctx context.Context) (rooms []newreleases.MatrixRoom, err error) 63 | } 64 | 65 | func printMatrixRoomsTable(cmd *cobra.Command, rooms []newreleases.MatrixRoom) { 66 | table := newTable(cmd.OutOrStdout()) 67 | table.SetHeader([]string{"ID", "Name", "Homeserver URL", "Internal Room ID"}) 68 | for _, e := range rooms { 69 | table.Append([]string{e.ID, e.Name, e.HomeserverURL, e.InternalRoomID}) 70 | } 71 | table.Render() 72 | } 73 | -------------------------------------------------------------------------------- /newreleases/cmd/matrix_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "context" 11 | "testing" 12 | 13 | "newreleases.io/cmd/newreleases/cmd" 14 | "newreleases.io/newreleases" 15 | ) 16 | 17 | func TestMatrixCmd(t *testing.T) { 18 | for _, tc := range []struct { 19 | name string 20 | matrixRoomsService cmd.MatrixRoomsService 21 | wantOutput string 22 | wantError error 23 | }{ 24 | { 25 | name: "no rooms", 26 | matrixRoomsService: newMockMatrixRoomsService(nil, nil), 27 | wantOutput: "No Matrix Rooms found.\n", 28 | }, 29 | { 30 | name: "with rooms", 31 | matrixRoomsService: newMockMatrixRoomsService([]newreleases.MatrixRoom{ 32 | { 33 | ID: "znne04qO5y6acw7sg5a9b1pc9t16rpym8jwn", 34 | Name: "NewReleases", 35 | HomeserverURL: "https://matrix-client.matrix.org", 36 | InternalRoomID: "!CklbIcwhYKygGsulFi:matrix.org", 37 | }, 38 | { 39 | ID: "9t4qOp16gmcw7z8jwrpyc6anne0sn5y5a9b1", 40 | Name: "Awesome project", 41 | HomeserverURL: "https://matrix-client.example.com", 42 | InternalRoomID: "!CkGsIcwhKyulFYlbgi:example.com", 43 | }, 44 | }, nil), 45 | wantOutput: "ID NAME HOMESERVER URL INTERNAL ROOM ID \nznne04qO5y6acw7sg5a9b1pc9t16rpym8jwn NewReleases https://matrix-client.matrix.org !CklbIcwhYKygGsulFi:matrix.org \n9t4qOp16gmcw7z8jwrpyc6anne0sn5y5a9b1 Awesome project https://matrix-client.example.com !CkGsIcwhKyulFYlbgi:example.com \n", 46 | }, 47 | { 48 | name: "error", 49 | matrixRoomsService: newMockMatrixRoomsService(nil, errTest), 50 | wantError: errTest, 51 | }, 52 | } { 53 | t.Run(tc.name, func(t *testing.T) { 54 | var outputBuf bytes.Buffer 55 | if err := newCommand(t, 56 | cmd.WithArgs("matrix"), 57 | cmd.WithOutput(&outputBuf), 58 | cmd.WithMatrixRoomsService(tc.matrixRoomsService), 59 | ).Execute(); err != tc.wantError { 60 | t.Fatalf("got error %v, want %v", err, tc.wantError) 61 | } 62 | 63 | gotOutput := outputBuf.String() 64 | if gotOutput != tc.wantOutput { 65 | t.Errorf("got output %q, want %q", gotOutput, tc.wantOutput) 66 | } 67 | }) 68 | } 69 | } 70 | 71 | type mockMatrixRoomsService struct { 72 | rooms []newreleases.MatrixRoom 73 | err error 74 | } 75 | 76 | func newMockMatrixRoomsService(rooms []newreleases.MatrixRoom, err error) mockMatrixRoomsService { 77 | return mockMatrixRoomsService{rooms: rooms, err: err} 78 | } 79 | 80 | func (s mockMatrixRoomsService) List(ctx context.Context) ([]newreleases.MatrixRoom, error) { 81 | return s.rooms, s.err 82 | } 83 | -------------------------------------------------------------------------------- /newreleases/cmd/mattermost.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initMattermostCmd() (err error) { 16 | cmd := &cobra.Command{ 17 | Use: "mattermost", 18 | Short: "List Mattermost integrations", 19 | RunE: func(cmd *cobra.Command, args []string) (err error) { 20 | ctx, cancel := newClientContext(c.config) 21 | defer cancel() 22 | 23 | webhooks, err := c.mattermostWebhooksService.List(ctx) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | if len(webhooks) == 0 { 29 | cmd.Println("No Mattermost Webhooks found.") 30 | return nil 31 | } 32 | 33 | printWebhooksTable(cmd, webhooks) 34 | 35 | return nil 36 | }, 37 | PreRunE: func(cmd *cobra.Command, args []string) error { 38 | if err := addClientConfigOptions(cmd, c.config); err != nil { 39 | return err 40 | } 41 | return c.setMattermostWebhooksService(cmd, args) 42 | }, 43 | } 44 | 45 | c.root.AddCommand(cmd) 46 | return addClientFlags(cmd) 47 | } 48 | 49 | func (c *command) setMattermostWebhooksService(cmd *cobra.Command, args []string) (err error) { 50 | if c.mattermostWebhooksService != nil { 51 | return nil 52 | } 53 | client, err := c.getClient(cmd) 54 | if err != nil { 55 | return err 56 | } 57 | c.mattermostWebhooksService = client.MattermostWebhooks 58 | return nil 59 | } 60 | 61 | type mattermostWebhooksService interface { 62 | List(ctx context.Context) (webhooks []newreleases.Webhook, err error) 63 | } 64 | -------------------------------------------------------------------------------- /newreleases/cmd/mattermost_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "context" 11 | "testing" 12 | 13 | "newreleases.io/cmd/newreleases/cmd" 14 | "newreleases.io/newreleases" 15 | ) 16 | 17 | func TestMattermostCmd(t *testing.T) { 18 | for _, tc := range []struct { 19 | name string 20 | mattermostWebhooksService cmd.MattermostWebhooksService 21 | wantOutput string 22 | wantError error 23 | }{ 24 | { 25 | name: "no webhooks", 26 | mattermostWebhooksService: newMockMattermostWebhooksService(nil, nil), 27 | wantOutput: "No Mattermost Webhooks found.\n", 28 | }, 29 | { 30 | name: "with webhooks", 31 | mattermostWebhooksService: newMockMattermostWebhooksService([]newreleases.Webhook{ 32 | { 33 | ID: "4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1", 34 | Name: "NewReleases", 35 | }, 36 | { 37 | ID: "c6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1", 38 | Name: "Awesome project", 39 | }, 40 | }, nil), 41 | wantOutput: "ID NAME \n4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1 NewReleases \nc6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1 Awesome project \n", 42 | }, 43 | { 44 | name: "error", 45 | mattermostWebhooksService: newMockMattermostWebhooksService(nil, errTest), 46 | wantError: errTest, 47 | }, 48 | } { 49 | t.Run(tc.name, func(t *testing.T) { 50 | var outputBuf bytes.Buffer 51 | if err := newCommand(t, 52 | cmd.WithArgs("mattermost"), 53 | cmd.WithOutput(&outputBuf), 54 | cmd.WithMattermostWebhooksService(tc.mattermostWebhooksService), 55 | ).Execute(); err != tc.wantError { 56 | t.Fatalf("got error %v, want %v", err, tc.wantError) 57 | } 58 | 59 | gotOutput := outputBuf.String() 60 | if gotOutput != tc.wantOutput { 61 | t.Errorf("got output %q, want %q", gotOutput, tc.wantOutput) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | type mockMattermostWebhooksService struct { 68 | webhooks []newreleases.Webhook 69 | err error 70 | } 71 | 72 | func newMockMattermostWebhooksService(webhooks []newreleases.Webhook, err error) (s mockMattermostWebhooksService) { 73 | return mockMattermostWebhooksService{webhooks: webhooks, err: err} 74 | } 75 | 76 | func (s mockMattermostWebhooksService) List(ctx context.Context) (webhooks []newreleases.Webhook, err error) { 77 | return s.webhooks, s.err 78 | } 79 | -------------------------------------------------------------------------------- /newreleases/cmd/microsoft_teams.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initMicrosoftTeamsCmd() (err error) { 16 | cmd := &cobra.Command{ 17 | Use: "microsoft-teams", 18 | Short: "List Microsoft Teams integrations", 19 | RunE: func(cmd *cobra.Command, args []string) (err error) { 20 | ctx, cancel := newClientContext(c.config) 21 | defer cancel() 22 | 23 | webhooks, err := c.microsoftTeamsWebhooksService.List(ctx) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | if len(webhooks) == 0 { 29 | cmd.Println("No Microsoft Teams Webhooks found.") 30 | return nil 31 | } 32 | 33 | printWebhooksTable(cmd, webhooks) 34 | 35 | return nil 36 | }, 37 | PreRunE: func(cmd *cobra.Command, args []string) error { 38 | if err := addClientConfigOptions(cmd, c.config); err != nil { 39 | return err 40 | } 41 | return c.setMicrosoftTeamsWebhooksService(cmd, args) 42 | }, 43 | } 44 | 45 | c.root.AddCommand(cmd) 46 | return addClientFlags(cmd) 47 | } 48 | 49 | func (c *command) setMicrosoftTeamsWebhooksService(cmd *cobra.Command, args []string) (err error) { 50 | if c.microsoftTeamsWebhooksService != nil { 51 | return nil 52 | } 53 | client, err := c.getClient(cmd) 54 | if err != nil { 55 | return err 56 | } 57 | c.microsoftTeamsWebhooksService = client.MicrosoftTeamsWebhooks 58 | return nil 59 | } 60 | 61 | type microsoftTeamsWebhooksService interface { 62 | List(ctx context.Context) (webhooks []newreleases.Webhook, err error) 63 | } 64 | -------------------------------------------------------------------------------- /newreleases/cmd/microsoft_teams_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "context" 11 | "testing" 12 | 13 | "newreleases.io/cmd/newreleases/cmd" 14 | "newreleases.io/newreleases" 15 | ) 16 | 17 | func TestMicrosoftTeamsCmd(t *testing.T) { 18 | for _, tc := range []struct { 19 | name string 20 | microsoftTeamsWebhooksService cmd.MicrosoftTeamsWebhooksService 21 | wantOutput string 22 | wantError error 23 | }{ 24 | { 25 | name: "no webhooks", 26 | microsoftTeamsWebhooksService: newMockMicrosoftTeamsWebhooksService(nil, nil), 27 | wantOutput: "No Microsoft Teams Webhooks found.\n", 28 | }, 29 | { 30 | name: "with webhooks", 31 | microsoftTeamsWebhooksService: newMockMicrosoftTeamsWebhooksService([]newreleases.Webhook{ 32 | { 33 | ID: "4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1", 34 | Name: "NewReleases", 35 | }, 36 | { 37 | ID: "c6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1", 38 | Name: "Awesome project", 39 | }, 40 | }, nil), 41 | wantOutput: "ID NAME \n4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1 NewReleases \nc6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1 Awesome project \n", 42 | }, 43 | { 44 | name: "error", 45 | microsoftTeamsWebhooksService: newMockMicrosoftTeamsWebhooksService(nil, errTest), 46 | wantError: errTest, 47 | }, 48 | } { 49 | t.Run(tc.name, func(t *testing.T) { 50 | var outputBuf bytes.Buffer 51 | if err := newCommand(t, 52 | cmd.WithArgs("microsoft-teams"), 53 | cmd.WithOutput(&outputBuf), 54 | cmd.WithMicrosoftTeamsWebhooksService(tc.microsoftTeamsWebhooksService), 55 | ).Execute(); err != tc.wantError { 56 | t.Fatalf("got error %v, want %v", err, tc.wantError) 57 | } 58 | 59 | gotOutput := outputBuf.String() 60 | if gotOutput != tc.wantOutput { 61 | t.Errorf("got output %q, want %q", gotOutput, tc.wantOutput) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | type mockMicrosoftTeamsWebhooksService struct { 68 | webhooks []newreleases.Webhook 69 | err error 70 | } 71 | 72 | func newMockMicrosoftTeamsWebhooksService(webhooks []newreleases.Webhook, err error) (s mockMicrosoftTeamsWebhooksService) { 73 | return mockMicrosoftTeamsWebhooksService{webhooks: webhooks, err: err} 74 | } 75 | 76 | func (s mockMicrosoftTeamsWebhooksService) List(ctx context.Context) (webhooks []newreleases.Webhook, err error) { 77 | return s.webhooks, s.err 78 | } 79 | -------------------------------------------------------------------------------- /newreleases/cmd/project.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | "strings" 11 | "unicode" 12 | 13 | "github.com/spf13/cobra" 14 | "newreleases.io/newreleases" 15 | ) 16 | 17 | func (c *command) initProjectCmd() (err error) { 18 | cmd := &cobra.Command{ 19 | Use: "project", 20 | Short: "Manage tracked projects", 21 | } 22 | 23 | if err := c.initProjectListCmd(cmd); err != nil { 24 | return err 25 | } 26 | if err := c.initProjectSearchCmd(cmd); err != nil { 27 | return err 28 | } 29 | if err := c.initProjectGetCmd(cmd); err != nil { 30 | return err 31 | } 32 | if err := c.initProjectAddCmd(cmd); err != nil { 33 | return err 34 | } 35 | if err := c.initProjectUpdateCmd(cmd); err != nil { 36 | return err 37 | } 38 | if err := c.initProjectRemoveCmd(cmd); err != nil { 39 | return err 40 | } 41 | 42 | c.root.AddCommand(cmd) 43 | return nil 44 | } 45 | 46 | func (c *command) setProjectsService(cmd *cobra.Command, args []string) (err error) { 47 | if c.projectsService != nil { 48 | return nil 49 | } 50 | client, err := c.getClient(cmd) 51 | if err != nil { 52 | return err 53 | } 54 | c.projectsService = client.Projects 55 | return nil 56 | } 57 | 58 | type projectsService interface { 59 | List(ctx context.Context, o newreleases.ProjectListOptions) (projects []newreleases.Project, lastPage int, err error) 60 | Search(ctx context.Context, query, provider string) (projects []newreleases.Project, err error) 61 | GetByID(ctx context.Context, id string) (project *newreleases.Project, err error) 62 | GetByName(ctx context.Context, provider, name string) (project *newreleases.Project, err error) 63 | Add(ctx context.Context, provider, name string, o *newreleases.ProjectOptions) (project *newreleases.Project, err error) 64 | UpdateByID(ctx context.Context, id string, o *newreleases.ProjectOptions) (project *newreleases.Project, err error) 65 | UpdateByName(ctx context.Context, provider, name string, o *newreleases.ProjectOptions) (project *newreleases.Project, err error) 66 | DeleteByID(ctx context.Context, id string) (err error) 67 | DeleteByName(ctx context.Context, provider, name string) (err error) 68 | } 69 | 70 | func printProjectsTable(cmd *cobra.Command, projects []newreleases.Project) { 71 | table := newTable(cmd.OutOrStdout()) 72 | 73 | var ( 74 | hasEmailNotification bool 75 | hasSlack bool 76 | hasTelegram bool 77 | hasDiscord bool 78 | hasHangoutsChat bool 79 | hasMicrosoftTeams bool 80 | hasMattermost bool 81 | hasRocketchat bool 82 | hasMatrix bool 83 | hasWebhook bool 84 | hasExclusions bool 85 | hasInclusions bool 86 | hasExcludePrereleases bool 87 | hasExcludeUpdated bool 88 | hasNote bool 89 | hasTag bool 90 | ) 91 | for _, p := range projects { 92 | if p.EmailNotification != newreleases.EmailNotificationNone && p.EmailNotification != "" { 93 | hasEmailNotification = true 94 | } 95 | if len(p.SlackIDs) > 0 { 96 | hasSlack = true 97 | } 98 | if len(p.TelegramChatIDs) > 0 { 99 | hasTelegram = true 100 | } 101 | if len(p.DiscordIDs) > 0 { 102 | hasDiscord = true 103 | } 104 | if len(p.HangoutsChatWebhookIDs) > 0 { 105 | hasHangoutsChat = true 106 | } 107 | if len(p.MSTeamsWebhookIDs) > 0 { 108 | hasMicrosoftTeams = true 109 | } 110 | if len(p.MattermostWebhookIDs) > 0 { 111 | hasMattermost = true 112 | } 113 | if len(p.RocketchatWebhookIDs) > 0 { 114 | hasRocketchat = true 115 | } 116 | if len(p.MatrixRoomIDs) > 0 { 117 | hasMatrix = true 118 | } 119 | if len(p.WebhookIDs) > 0 { 120 | hasWebhook = true 121 | } 122 | if len(p.Exclusions) > 0 { 123 | for _, e := range p.Exclusions { 124 | if e.Inverse { 125 | hasInclusions = true 126 | } else { 127 | hasExclusions = true 128 | } 129 | } 130 | } 131 | if p.ExcludePrereleases { 132 | hasExcludePrereleases = true 133 | } 134 | if p.ExcludeUpdated { 135 | hasExcludeUpdated = true 136 | } 137 | if len(p.Note) > 0 { 138 | hasNote = true 139 | } 140 | if len(p.TagIDs) > 0 { 141 | hasTag = true 142 | } 143 | } 144 | 145 | header := []string{ 146 | "ID", 147 | "Name", 148 | "Provider", 149 | } 150 | if hasEmailNotification { 151 | header = append(header, "Email") 152 | } 153 | if hasSlack { 154 | header = append(header, "Slack") 155 | } 156 | if hasTelegram { 157 | header = append(header, "Telegram") 158 | } 159 | if hasDiscord { 160 | header = append(header, "Discord") 161 | } 162 | if hasHangoutsChat { 163 | header = append(header, "Hangouts Chat") 164 | } 165 | if hasMicrosoftTeams { 166 | header = append(header, "Microsoft Teams") 167 | } 168 | if hasMattermost { 169 | header = append(header, "Mattermost") 170 | } 171 | if hasRocketchat { 172 | header = append(header, "Rocket.Chat") 173 | } 174 | if hasMatrix { 175 | header = append(header, "Matrix") 176 | } 177 | if hasWebhook { 178 | header = append(header, "Webhook") 179 | } 180 | if hasExclusions { 181 | header = append(header, "Regex Exclude") 182 | } 183 | if hasInclusions { 184 | header = append(header, "Regex Exclude Inverse") 185 | } 186 | if hasExcludePrereleases { 187 | header = append(header, "Exclude Pre-Releases") 188 | } 189 | if hasExcludeUpdated { 190 | header = append(header, "Exclude Updated") 191 | } 192 | if hasNote { 193 | header = append(header, "Note") 194 | } 195 | if hasTag { 196 | header = append(header, "Tags") 197 | } 198 | table.SetHeader(header) 199 | for _, p := range projects { 200 | r := []string{p.ID, p.Name, p.Provider} 201 | if hasEmailNotification { 202 | r = append(r, string(p.EmailNotification)) 203 | } 204 | if hasSlack { 205 | r = append(r, strings.Join(p.SlackIDs, ", ")) 206 | } 207 | if hasTelegram { 208 | r = append(r, strings.Join(p.TelegramChatIDs, ", ")) 209 | } 210 | if hasDiscord { 211 | r = append(r, strings.Join(p.DiscordIDs, ", ")) 212 | } 213 | if hasHangoutsChat { 214 | r = append(r, strings.Join(p.HangoutsChatWebhookIDs, ", ")) 215 | } 216 | if hasMicrosoftTeams { 217 | r = append(r, strings.Join(p.MSTeamsWebhookIDs, ", ")) 218 | } 219 | if hasMattermost { 220 | r = append(r, strings.Join(p.MattermostWebhookIDs, ", ")) 221 | } 222 | if hasRocketchat { 223 | r = append(r, strings.Join(p.RocketchatWebhookIDs, ", ")) 224 | } 225 | if hasMatrix { 226 | r = append(r, strings.Join(p.MatrixRoomIDs, ", ")) 227 | } 228 | if hasWebhook { 229 | r = append(r, strings.Join(p.WebhookIDs, ", ")) 230 | } 231 | if hasExclusions { 232 | var l []string 233 | for _, e := range p.Exclusions { 234 | if !e.Inverse { 235 | l = append(l, e.Value) 236 | } 237 | } 238 | r = append(r, strings.Join(l, ", ")) 239 | } 240 | if hasInclusions { 241 | var l []string 242 | for _, e := range p.Exclusions { 243 | if e.Inverse { 244 | l = append(l, e.Value) 245 | } 246 | } 247 | r = append(r, strings.Join(l, ", ")) 248 | } 249 | if hasExcludePrereleases { 250 | r = append(r, yesNo(p.ExcludePrereleases)) 251 | } 252 | if hasExcludeUpdated { 253 | r = append(r, yesNo(p.ExcludeUpdated)) 254 | } 255 | if hasNote { 256 | note := p.Note 257 | if len(note) > 10 { 258 | note = strings.TrimRightFunc(strings.TrimSpace(note[:10]), unicode.IsSymbol) + "..." 259 | } 260 | r = append(r, note) 261 | } 262 | if hasTag { 263 | r = append(r, strings.Join(p.TagIDs, ", ")) 264 | } 265 | table.Append(r) 266 | } 267 | table.Render() 268 | } 269 | 270 | func printProject(cmd *cobra.Command, p *newreleases.Project) { 271 | table := newTable(cmd.OutOrStdout()) 272 | table.Append([]string{"ID:", p.ID}) 273 | table.Append([]string{"Name:", p.Name}) 274 | table.Append([]string{"Provider:", p.Provider}) 275 | if p.EmailNotification != newreleases.EmailNotificationNone && p.EmailNotification != "" { 276 | table.Append([]string{"Email:", string(p.EmailNotification)}) 277 | } 278 | if len(p.SlackIDs) > 0 { 279 | table.Append([]string{"Slack:", strings.Join(p.SlackIDs, ", ")}) 280 | } 281 | if len(p.TelegramChatIDs) > 0 { 282 | table.Append([]string{"Telegram:", strings.Join(p.TelegramChatIDs, ", ")}) 283 | } 284 | if len(p.DiscordIDs) > 0 { 285 | table.Append([]string{"Discord:", strings.Join(p.DiscordIDs, ", ")}) 286 | } 287 | if len(p.HangoutsChatWebhookIDs) > 0 { 288 | table.Append([]string{"Hangouts Chat:", strings.Join(p.HangoutsChatWebhookIDs, ", ")}) 289 | } 290 | if len(p.MSTeamsWebhookIDs) > 0 { 291 | table.Append([]string{"Microsoft Teams:", strings.Join(p.MSTeamsWebhookIDs, ", ")}) 292 | } 293 | if len(p.MattermostWebhookIDs) > 0 { 294 | table.Append([]string{"Mattermost:", strings.Join(p.MattermostWebhookIDs, ", ")}) 295 | } 296 | if len(p.RocketchatWebhookIDs) > 0 { 297 | table.Append([]string{"Rocket.Chat:", strings.Join(p.RocketchatWebhookIDs, ", ")}) 298 | } 299 | if len(p.MatrixRoomIDs) > 0 { 300 | table.Append([]string{"Matrix:", strings.Join(p.MatrixRoomIDs, ", ")}) 301 | } 302 | if len(p.WebhookIDs) > 0 { 303 | table.Append([]string{"Webhooks:", strings.Join(p.WebhookIDs, ", ")}) 304 | } 305 | var excluded, excludedInverse []string 306 | for _, e := range p.Exclusions { 307 | if e.Inverse { 308 | excludedInverse = append(excludedInverse, e.Value) 309 | } else { 310 | excluded = append(excluded, e.Value) 311 | } 312 | } 313 | if len(excluded) > 0 { 314 | table.Append([]string{"Regex Exclude:", strings.Join(excluded, ", ")}) 315 | } 316 | if len(excludedInverse) > 0 { 317 | table.Append([]string{"Regex Exclude Inverse:", strings.Join(excludedInverse, ", ")}) 318 | } 319 | if p.ExcludePrereleases { 320 | table.Append([]string{"Exclude Pre-Releases:", "yes"}) 321 | } 322 | if p.ExcludeUpdated { 323 | table.Append([]string{"Exclude Updated:", "yes"}) 324 | } 325 | if len(p.Note) > 0 { 326 | table.Append([]string{"Note:", p.Note}) 327 | } 328 | if len(p.TagIDs) > 0 { 329 | table.Append([]string{"Tags:", strings.Join(p.TagIDs, ", ")}) 330 | } 331 | table.Render() 332 | } 333 | -------------------------------------------------------------------------------- /newreleases/cmd/project_add.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "strings" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initProjectAddCmd(projectCmd *cobra.Command) (err error) { 16 | var ( 17 | optionNameEmail = "email" 18 | optionNameSlack = "slack" 19 | optionNameTelegram = "telegram" 20 | optionNameDiscord = "discord" 21 | optionNameHangoutsChat = "hangouts-chat" 22 | optionNameMicrosoftTeams = "microsoft-teams" 23 | optionNameMattermost = "mattermost" 24 | optionNameRocketchat = "rocketchat" 25 | optionNameMatrix = "matrix" 26 | optionNameWebhook = "webhook" 27 | optionNameExclusions = "regex-exclude" 28 | optionNameExcludePrereleases = "exclude-prereleases" 29 | optionNameExcludeUpdated = "exclude-updated" 30 | optionNameNote = "note" 31 | optionNameTag = "tag" 32 | ) 33 | 34 | cmd := &cobra.Command{ 35 | Use: "add PROVIDER PROJECT_NAME", 36 | Short: "Add a project to track", 37 | RunE: func(cmd *cobra.Command, args []string) (err error) { 38 | ctx, cancel := newClientContext(c.config) 39 | defer cancel() 40 | 41 | if len(args) != 2 { 42 | return cmd.Help() 43 | } 44 | 45 | o := &newreleases.ProjectOptions{} 46 | 47 | flags := cmd.Flags() 48 | email, err := flags.GetString(optionNameEmail) 49 | if err != nil { 50 | return err 51 | } 52 | if email != "" { 53 | e := newreleases.EmailNotification(email) 54 | o.EmailNotification = &e 55 | } 56 | o.SlackIDs, err = flags.GetStringArray(optionNameSlack) 57 | if err != nil { 58 | return err 59 | } 60 | o.TelegramChatIDs, err = flags.GetStringArray(optionNameTelegram) 61 | if err != nil { 62 | return err 63 | } 64 | o.DiscordIDs, err = flags.GetStringArray(optionNameDiscord) 65 | if err != nil { 66 | return err 67 | } 68 | o.HangoutsChatWebhookIDs, err = flags.GetStringArray(optionNameHangoutsChat) 69 | if err != nil { 70 | return err 71 | } 72 | o.MSTeamsWebhookIDs, err = flags.GetStringArray(optionNameMicrosoftTeams) 73 | if err != nil { 74 | return err 75 | } 76 | o.MattermostWebhookIDs, err = flags.GetStringArray(optionNameMattermost) 77 | if err != nil { 78 | return err 79 | } 80 | o.RocketchatWebhookIDs, err = flags.GetStringArray(optionNameRocketchat) 81 | if err != nil { 82 | return err 83 | } 84 | o.MatrixRoomIDs, err = flags.GetStringArray(optionNameMatrix) 85 | if err != nil { 86 | return err 87 | } 88 | o.WebhookIDs, err = flags.GetStringArray(optionNameWebhook) 89 | if err != nil { 90 | return err 91 | } 92 | exclusions, err := flags.GetStringArray(optionNameExclusions) 93 | if err != nil { 94 | return err 95 | } 96 | for _, v := range exclusions { 97 | var inverse bool 98 | if strings.HasSuffix(v, "-inverse") { 99 | inverse = true 100 | v = strings.TrimSuffix(v, "-inverse") 101 | } 102 | o.Exclusions = append(o.Exclusions, newreleases.Exclusion{ 103 | Value: v, 104 | Inverse: inverse, 105 | }) 106 | } 107 | if flags.Changed(optionNameExcludePrereleases) { 108 | excludePrereleases, err := flags.GetBool(optionNameExcludePrereleases) 109 | if err != nil { 110 | return err 111 | } 112 | o.ExcludePrereleases = &excludePrereleases 113 | } 114 | if flags.Changed(optionNameExcludeUpdated) { 115 | excludeUpdated, err := flags.GetBool(optionNameExcludeUpdated) 116 | if err != nil { 117 | return err 118 | } 119 | o.ExcludeUpdated = &excludeUpdated 120 | } 121 | o.TagIDs, err = flags.GetStringArray(optionNameTag) 122 | if err != nil { 123 | return err 124 | } 125 | if flags.Changed(optionNameNote) { 126 | note, err := flags.GetString(optionNameNote) 127 | if err != nil { 128 | return err 129 | } 130 | o.Note = ¬e 131 | } 132 | 133 | project, err := c.projectsService.Add(ctx, args[0], args[1], o) 134 | if err != nil { 135 | return err 136 | } 137 | 138 | printProject(cmd, project) 139 | return nil 140 | }, 141 | PreRunE: func(cmd *cobra.Command, args []string) error { 142 | if err := addClientConfigOptions(cmd, c.config); err != nil { 143 | return err 144 | } 145 | return c.setProjectsService(cmd, args) 146 | }, 147 | } 148 | 149 | cmd.Flags().String(optionNameEmail, "none", "frequency of email notifications: instant, hourly, daily, weekly, none") 150 | cmd.Flags().StringArray(optionNameSlack, nil, "Slack channel ID") 151 | cmd.Flags().StringArray(optionNameTelegram, nil, "Telegram chat ID") 152 | cmd.Flags().StringArray(optionNameDiscord, nil, "Discord channel ID") 153 | cmd.Flags().StringArray(optionNameHangoutsChat, nil, "Hangouts Chat webhook ID") 154 | cmd.Flags().StringArray(optionNameMicrosoftTeams, nil, "Microsoft Teams webhook ID") 155 | cmd.Flags().StringArray(optionNameMattermost, nil, "Mattermost webhook ID") 156 | cmd.Flags().StringArray(optionNameRocketchat, nil, "Rocket.Chat webhook ID") 157 | cmd.Flags().StringArray(optionNameMatrix, nil, "Matrix room ID") 158 | cmd.Flags().StringArray(optionNameWebhook, nil, "Webhook ID") 159 | cmd.Flags().StringArray(optionNameExclusions, nil, "Regex version exclusion, suffix with \"-inverse\" for inclusion") 160 | cmd.Flags().Bool(optionNameExcludePrereleases, false, "exclude pre-releases") 161 | cmd.Flags().Bool(optionNameExcludeUpdated, false, "exclude updated") 162 | cmd.Flags().StringArray(optionNameTag, nil, "Tag ID") 163 | cmd.Flags().String(optionNameNote, "", "Note") 164 | 165 | projectCmd.AddCommand(cmd) 166 | return addClientFlags(cmd) 167 | } 168 | -------------------------------------------------------------------------------- /newreleases/cmd/project_add_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. { 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "testing" 11 | 12 | "newreleases.io/cmd/newreleases/cmd" 13 | ) 14 | 15 | func TestProjectCmd_Add(t *testing.T) { 16 | for _, tc := range []struct { 17 | name string 18 | args []string 19 | projectsService cmd.ProjectsService 20 | wantOutput string 21 | wantError error 22 | }{ 23 | { 24 | name: "minimal", 25 | args: []string{"github", "golang/go"}, 26 | projectsService: newMockProjectsService(1, nil), 27 | wantOutput: "ID: new \nName: golang/go \nProvider: github \n", 28 | }, 29 | { 30 | name: "full", 31 | args: []string{ 32 | "github", "golang/go", 33 | "--email", "weekly", 34 | "--slack", "mdsbe60td5gwgzetyksdfeyxt4", 35 | "--telegram", "sdfeyxt4mdsbe60td5gwgzetyk", 36 | "--discord", "4mdsbe60td5gwgzetyksdfeyxt", 37 | "--discord", "zext4mdsbe6tyksdfey0td5gwg", 38 | "--hangouts-chat", "etyksdfeyxt4mdsbe60td5gwgz", 39 | "--microsoft-teams", "0td5gwgzextbe6tyksdfey4mds", 40 | "--mattermost", "wgxtzed4yksd5dfeymsbe6t0tg", 41 | "--rocketchat", "5dfeymsbe6t0tgwgxtzed4yksd", 42 | "--matrix", "wgxtzesbe6t05dfed4yksdmytg", 43 | "--webhook", "tbe6tyksdfey4md0td5gwgzexs", 44 | "--regex-exclude", `^0\.1`, 45 | "--regex-exclude", `^0\.3-inverse`, 46 | "--exclude-prereleases", 47 | "--exclude-updated", 48 | "--note", "Some note", 49 | "--tag", "33f1db7254b9", 50 | }, 51 | projectsService: newMockProjectsService(1, nil), 52 | wantOutput: "ID: new \nName: golang/go \nProvider: github \nEmail: weekly \nSlack: mdsbe60td5gwgzetyksdfeyxt4 \nTelegram: sdfeyxt4mdsbe60td5gwgzetyk \nDiscord: 4mdsbe60td5gwgzetyksdfeyxt, zext4mdsbe6tyksdfey0td5gwg \nHangouts Chat: etyksdfeyxt4mdsbe60td5gwgz \nMicrosoft Teams: 0td5gwgzextbe6tyksdfey4mds \nMattermost: wgxtzed4yksd5dfeymsbe6t0tg \nRocket.Chat: 5dfeymsbe6t0tgwgxtzed4yksd \nMatrix: wgxtzesbe6t05dfed4yksdmytg \nWebhooks: tbe6tyksdfey4md0td5gwgzexs \nRegex Exclude: ^0\\.1 \nRegex Exclude Inverse: ^0\\.3 \nExclude Pre-Releases: yes \nExclude Updated: yes \nNote: Some note \nTags: 33f1db7254b9 \n", 53 | }, 54 | { 55 | name: "error", 56 | args: []string{"github", "golang/go"}, 57 | projectsService: newMockProjectsService(1, errTest), 58 | wantError: errTest, 59 | }, 60 | } { 61 | t.Run(tc.name, func(t *testing.T) { 62 | var outputBuf bytes.Buffer 63 | if err := newCommand(t, 64 | cmd.WithArgs(append([]string{"project", "add"}, tc.args...)...), 65 | cmd.WithOutput(&outputBuf), 66 | cmd.WithProjectsService(tc.projectsService), 67 | ).Execute(); err != tc.wantError { 68 | t.Fatalf("got error %v, want %v", err, tc.wantError) 69 | } 70 | 71 | wantOutput := trimSpace(tc.wantOutput) 72 | gotOutput := trimSpace(outputBuf.String()) 73 | if gotOutput != wantOutput { 74 | t.Errorf("got output %q, want %q", gotOutput, wantOutput) 75 | } 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /newreleases/cmd/project_get.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "github.com/spf13/cobra" 10 | "newreleases.io/newreleases" 11 | ) 12 | 13 | func (c *command) initProjectGetCmd(projectCmd *cobra.Command) (err error) { 14 | cmd := &cobra.Command{ 15 | Use: "get [PROVIDER PROJECT_NAME] | [PROJECT_ID]", 16 | Short: "Get information about a tracked project", 17 | RunE: func(cmd *cobra.Command, args []string) (err error) { 18 | ctx, cancel := newClientContext(c.config) 19 | defer cancel() 20 | 21 | var project *newreleases.Project 22 | switch len(args) { 23 | case 1: 24 | project, err = c.projectsService.GetByID(ctx, args[0]) 25 | case 2: 26 | project, err = c.projectsService.GetByName(ctx, args[0], args[1]) 27 | default: 28 | return cmd.Help() 29 | } 30 | if err != nil { 31 | return err 32 | } 33 | 34 | if project == nil || err == newreleases.ErrNotFound { 35 | cmd.Println("Project not found.") 36 | return nil 37 | } 38 | 39 | printProject(cmd, project) 40 | return nil 41 | }, 42 | PreRunE: func(cmd *cobra.Command, args []string) error { 43 | if err := addClientConfigOptions(cmd, c.config); err != nil { 44 | return err 45 | } 46 | return c.setProjectsService(cmd, args) 47 | }, 48 | } 49 | 50 | projectCmd.AddCommand(cmd) 51 | return addClientFlags(cmd) 52 | } 53 | -------------------------------------------------------------------------------- /newreleases/cmd/project_get_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. { 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "testing" 11 | 12 | "newreleases.io/cmd/newreleases/cmd" 13 | "newreleases.io/newreleases" 14 | ) 15 | 16 | func TestProjectCmd_Get(t *testing.T) { 17 | for _, tc := range []struct { 18 | name string 19 | projectsService cmd.ProjectsService 20 | wantOutput string 21 | wantError error 22 | }{ 23 | { 24 | name: "no project", 25 | projectsService: newMockProjectsService(1, nil), 26 | wantOutput: "Project not found.\n", 27 | }, 28 | { 29 | name: "minimal project", 30 | projectsService: newMockProjectsService(1, nil, []newreleases.Project{minimalProject}), 31 | wantOutput: "ID: mdsbe60td5gwgzetyksdfeyxt4 \nName: golang/go \nProvider: github \n", 32 | }, 33 | { 34 | name: "full project", 35 | projectsService: newMockProjectsService(1, nil, []newreleases.Project{fullProject}), 36 | wantOutput: "ID: mdsbe60td5gwgzetyksdfeyxt4 \nName: golang/go \nProvider: github \nEmail: daily \nSlack: zetyksdfeymdsbe60td5gwgxt4 \nTelegram: sbe60td5gwgxtzetyksdfeymd4 \nDiscord: tyksdfeymsbegxtzed460td5gw \nHangouts Chat: yksdfeymsbe6t0td5gzed4wgxt \nMicrosoft Teams: gwgxtzed4yksdfeymsbe6t0td5 \nMattermost: wgxtzed4yksd5dfeymsbe6t0tg \nRocket.Chat: 5dfeymsbe6t0tgwgxtzed4yksd \nMatrix: 4yksd5e6twgxtzdfeymsbed0tg \nWebhooks: e6t0td5ykgwgxtzed4eymsbsdf \nRegex Exclude: ^0\\.1 \nRegex Exclude Inverse: ^0\\.3 \nExclude Pre-Releases: yes \nExclude Updated: yes \nNote: Initial note \nTags: 33f1db7254b9 \n", 37 | }, 38 | { 39 | name: "error", 40 | projectsService: newMockProjectsService(1, errTest), 41 | wantError: errTest, 42 | }, 43 | } { 44 | t.Run(tc.name, func(t *testing.T) { 45 | t.Run("by id", func(t *testing.T) { 46 | var outputBuf bytes.Buffer 47 | if err := newCommand(t, 48 | cmd.WithArgs("project", "get", "mdsbe60td5gwgzetyksdfeyxt4"), 49 | cmd.WithOutput(&outputBuf), 50 | cmd.WithProjectsService(tc.projectsService), 51 | ).Execute(); err != tc.wantError { 52 | t.Fatalf("got error %v, want %v", err, tc.wantError) 53 | } 54 | 55 | wantOutput := trimSpace(tc.wantOutput) 56 | gotOutput := trimSpace(outputBuf.String()) 57 | if gotOutput != wantOutput { 58 | t.Errorf("got output %q, want %q", gotOutput, wantOutput) 59 | } 60 | }) 61 | t.Run("by name", func(t *testing.T) { 62 | var outputBuf bytes.Buffer 63 | if err := newCommand(t, 64 | cmd.WithArgs("project", "get", "github", "golang/go"), 65 | cmd.WithOutput(&outputBuf), 66 | cmd.WithProjectsService(tc.projectsService), 67 | ).Execute(); err != tc.wantError { 68 | t.Fatalf("got error %v, want %v", err, tc.wantError) 69 | } 70 | 71 | wantOutput := trimSpace(tc.wantOutput) 72 | gotOutput := trimSpace(outputBuf.String()) 73 | if gotOutput != wantOutput { 74 | t.Errorf("got output %q, want %q", gotOutput, wantOutput) 75 | } 76 | }) 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /newreleases/cmd/project_list.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "github.com/spf13/cobra" 10 | "newreleases.io/newreleases" 11 | ) 12 | 13 | func (c *command) initProjectListCmd(projectCmd *cobra.Command) (err error) { 14 | var ( 15 | optionNamePage = "page" 16 | optionNameProvider = "provider" 17 | optionNameTagID = "tag" 18 | optionNameOrder = "order" 19 | ) 20 | 21 | cmd := &cobra.Command{ 22 | Use: "list", 23 | Short: "Get tracked projects", 24 | RunE: func(cmd *cobra.Command, args []string) (err error) { 25 | ctx, cancel := newClientContext(c.config) 26 | defer cancel() 27 | 28 | flags := cmd.Flags() 29 | page, err := flags.GetInt(optionNamePage) 30 | if err != nil { 31 | return err 32 | } 33 | provider, err := flags.GetString(optionNameProvider) 34 | if err != nil { 35 | return err 36 | } 37 | tagID, err := flags.GetString(optionNameTagID) 38 | if err != nil { 39 | return err 40 | } 41 | order, err := flags.GetString(optionNameOrder) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | o := newreleases.ProjectListOptions{ 47 | Page: page, 48 | Provider: provider, 49 | TagID: tagID, 50 | } 51 | if order != "" { 52 | o.Order = newreleases.ProjectListOrder(order) 53 | } 54 | 55 | projects, lastPage, err := c.projectsService.List(ctx, o) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | if len(projects) == 0 || err == newreleases.ErrNotFound { 61 | if page <= 1 { 62 | cmd.Println("No projects found.") 63 | return nil 64 | } 65 | cmd.Printf("No projects found on page %v.\n", page) 66 | return nil 67 | } 68 | 69 | printProjectsTable(cmd, projects) 70 | 71 | if page < lastPage { 72 | cmd.Println("More projects on the next page...") 73 | } 74 | 75 | return nil 76 | }, 77 | PreRunE: func(cmd *cobra.Command, args []string) error { 78 | if err := addClientConfigOptions(cmd, c.config); err != nil { 79 | return err 80 | } 81 | return c.setProjectsService(cmd, args) 82 | }, 83 | } 84 | 85 | cmd.Flags().IntP(optionNamePage, "p", 1, "page number") 86 | cmd.Flags().String(optionNameProvider, "", "filter by provider") 87 | cmd.Flags().String(optionNameOrder, "", "sort projects: updated, added, name; default updated") 88 | cmd.Flags().String(optionNameTagID, "", "filter by tag ID") 89 | 90 | projectCmd.AddCommand(cmd) 91 | return addClientFlags(cmd) 92 | } 93 | -------------------------------------------------------------------------------- /newreleases/cmd/project_list_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. { 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "testing" 11 | 12 | "newreleases.io/cmd/newreleases/cmd" 13 | "newreleases.io/newreleases" 14 | ) 15 | 16 | func TestProjectCmd_List(t *testing.T) { 17 | for _, tc := range []struct { 18 | name string 19 | args []string 20 | projectsService cmd.ProjectsService 21 | wantOutput string 22 | wantError error 23 | }{ 24 | { 25 | name: "no projects", 26 | projectsService: newMockProjectsService(1, nil), 27 | wantOutput: "No projects found.\n", 28 | }, 29 | { 30 | name: "minimal project", 31 | projectsService: newMockProjectsService(1, nil, []newreleases.Project{ 32 | {ID: "mdsbe60td5gwgzetyksdfeyxt4", Name: "golang/go", Provider: "github"}, 33 | }), 34 | wantOutput: "ID NAME PROVIDER \nmdsbe60td5gwgzetyksdfeyxt4 golang/go github \n", 35 | }, 36 | { 37 | name: "projects page 2", 38 | args: []string{"--page", "2"}, 39 | projectsService: newMockProjectsService(1, nil, 40 | []newreleases.Project{ 41 | {ID: "mdsbe60td5gwgzetyksdfeyxt4", Name: "golang/go", Provider: "github"}, 42 | }, 43 | []newreleases.Project{ 44 | {ID: "myxtdsbe60td5gwgzetyksdfe4", Name: "newreleases/cli-go", Provider: "github"}, 45 | }, 46 | ), 47 | wantOutput: "ID NAME PROVIDER \nmyxtdsbe60td5gwgzetyksdfe4 newreleases/cli-go github \n", 48 | }, 49 | { 50 | name: "projects filter by provider", 51 | args: []string{"--provider", "github"}, 52 | projectsService: newMockProjectsService(1, nil, []newreleases.Project{ 53 | {ID: "mdsbe60td5gwgzetyksdfeyxt4", Name: "golang/go", Provider: "github"}, 54 | {ID: "ksdfeyxt4mdsbe60td5gwgzety", Name: "newreleases/cli-go", Provider: "github"}, 55 | {ID: "gwgzetyksdfeyxt4mdsbe60td5", Name: "vue", Provider: "npm"}, 56 | }), 57 | wantOutput: "ID NAME PROVIDER \nmdsbe60td5gwgzetyksdfeyxt4 golang/go github \nksdfeyxt4mdsbe60td5gwgzety newreleases/cli-go github \n", 58 | }, 59 | { 60 | name: "projects order by name", 61 | args: []string{"--order", "name"}, 62 | projectsService: newMockProjectsService(1, nil, []newreleases.Project{ 63 | {ID: "gwgzetyksdfeyxt4mdsbe60td5", Name: "vue", Provider: "npm"}, 64 | {ID: "mdsbe60td5gwgzetyksdfeyxt4", Name: "golang/go", Provider: "github"}, 65 | {ID: "ksdfeyxt4mdsbe60td5gwgzety", Name: "newreleases/cli-go", Provider: "github"}, 66 | }), 67 | wantOutput: "ID NAME PROVIDER \nmdsbe60td5gwgzetyksdfeyxt4 golang/go github \nksdfeyxt4mdsbe60td5gwgzety newreleases/cli-go github \ngwgzetyksdfeyxt4mdsbe60td5 vue npm \n", 68 | }, 69 | { 70 | name: "projects order by added time", 71 | args: []string{"--order", "added"}, 72 | projectsService: newMockProjectsService(1, nil, []newreleases.Project{ 73 | {ID: "gwgzetyksdfeyxt4mdsbe60td5", Name: "vue", Provider: "npm"}, 74 | {ID: "mdsbe60td5gwgzetyksdfeyxt4", Name: "golang/go", Provider: "github"}, 75 | {ID: "ksdfeyxt4mdsbe60td5gwgzety", Name: "newreleases/cli-go", Provider: "github"}, 76 | }), 77 | wantOutput: "ID NAME PROVIDER \ngwgzetyksdfeyxt4mdsbe60td5 vue npm \nksdfeyxt4mdsbe60td5gwgzety newreleases/cli-go github \nmdsbe60td5gwgzetyksdfeyxt4 golang/go github \n", 78 | }, 79 | { 80 | name: "tag id", 81 | args: []string{"--tag", "123456"}, 82 | projectsService: newMockProjectsService(1, nil, []newreleases.Project{ 83 | {ID: "gwgzetyksdfeyxt4mdsbe60td5", Name: "vue", Provider: "npm", TagIDs: []string{"123456"}}, 84 | {ID: "mdsbe60td5gwgzetyksdfeyxt4", Name: "golang/go", Provider: "github", TagIDs: []string{"123456", "345678"}}, 85 | {ID: "ksdfeyxt4mdsbe60td5gwgzety", Name: "newreleases/cli-go", Provider: "github"}, 86 | }), 87 | wantOutput: "ID NAME PROVIDER TAGS\ngwgzetyksdfeyxt4mdsbe60td5 vue npm 123456 \nmdsbe60td5gwgzetyksdfeyxt4 golang/go github 123456, 345678 \n", 88 | }, 89 | { 90 | name: "full project", 91 | projectsService: newMockProjectsService(1, nil, []newreleases.Project{fullProject}), 92 | wantOutput: "ID NAME PROVIDER EMAIL SLACK TELEGRAM DISCORD HANGOUTS CHAT MICROSOFT TEAMS MATTERMOST ROCKET CHAT MATRIX WEBHOOK REGEX EXCLUDE REGEX EXCLUDE INVERSE EXCLUDE PRE-RELEASES EXCLUDE UPDATED NOTE TAGS \nmdsbe60td5gwgzetyksdfeyxt4 golang/go github daily zetyksdfeymdsbe60td5gwgxt4 sbe60td5gwgxtzetyksdfeymd4 tyksdfeymsbegxtzed460td5gw yksdfeymsbe6t0td5gzed4wgxt gwgxtzed4yksdfeymsbe6t0td5 wgxtzed4yksd5dfeymsbe6t0tg 5dfeymsbe6t0tgwgxtzed4yksd 4yksd5e6twgxtzdfeymsbed0tg e6t0td5ykgwgxtzed4eymsbsdf ^0\\.1 ^0\\.3 yes yes Initial no... 33f1db7254b9 \n", 93 | }, 94 | { 95 | name: "error", 96 | projectsService: newMockProjectsService(1, errTest), 97 | wantError: errTest, 98 | }, 99 | } { 100 | t.Run(tc.name, func(t *testing.T) { 101 | var outputBuf bytes.Buffer 102 | if err := newCommand(t, 103 | cmd.WithArgs(append([]string{"project", "list"}, tc.args...)...), 104 | cmd.WithOutput(&outputBuf), 105 | cmd.WithProjectsService(tc.projectsService), 106 | ).Execute(); err != tc.wantError { 107 | t.Fatalf("got error %v, want %v", err, tc.wantError) 108 | } 109 | 110 | wantOutput := trimSpace(tc.wantOutput) 111 | gotOutput := trimSpace(outputBuf.String()) 112 | if gotOutput != wantOutput { 113 | t.Errorf("got output %q, want %q", gotOutput, wantOutput) 114 | } 115 | }) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /newreleases/cmd/project_remove.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "github.com/spf13/cobra" 10 | "newreleases.io/newreleases" 11 | ) 12 | 13 | func (c *command) initProjectRemoveCmd(projectCmd *cobra.Command) (err error) { 14 | cmd := &cobra.Command{ 15 | Use: "remove [PROVIDER PROJECT_NAME] | [PROJECT_ID]", 16 | Short: "Remove a tracked project", 17 | RunE: func(cmd *cobra.Command, args []string) (err error) { 18 | ctx, cancel := newClientContext(c.config) 19 | defer cancel() 20 | 21 | switch len(args) { 22 | case 1: 23 | err = c.projectsService.DeleteByID(ctx, args[0]) 24 | case 2: 25 | err = c.projectsService.DeleteByName(ctx, args[0], args[1]) 26 | default: 27 | return cmd.Help() 28 | } 29 | 30 | if err == newreleases.ErrNotFound { 31 | cmd.Println("Project not found.") 32 | return nil 33 | } 34 | 35 | return err 36 | }, 37 | PreRunE: func(cmd *cobra.Command, args []string) error { 38 | if err := addClientConfigOptions(cmd, c.config); err != nil { 39 | return err 40 | } 41 | return c.setProjectsService(cmd, args) 42 | }, 43 | } 44 | 45 | projectCmd.AddCommand(cmd) 46 | return addClientFlags(cmd) 47 | } 48 | -------------------------------------------------------------------------------- /newreleases/cmd/project_remove_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. { 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "testing" 11 | 12 | "newreleases.io/cmd/newreleases/cmd" 13 | "newreleases.io/newreleases" 14 | ) 15 | 16 | func TestProjectCmd_Remove(t *testing.T) { 17 | for _, tc := range []struct { 18 | name string 19 | projectsService cmd.ProjectsService 20 | wantOutput string 21 | wantError error 22 | }{ 23 | { 24 | name: "no project", 25 | projectsService: newMockProjectsService(1, newreleases.ErrNotFound), 26 | wantOutput: "Project not found.\n", 27 | }, 28 | { 29 | name: "project", 30 | projectsService: newMockProjectsService(1, nil, []newreleases.Project{minimalProject}), 31 | wantOutput: "", 32 | }, 33 | { 34 | name: "error", 35 | projectsService: newMockProjectsService(1, errTest), 36 | wantError: errTest, 37 | }, 38 | } { 39 | t.Run(tc.name, func(t *testing.T) { 40 | t.Run("by id", func(t *testing.T) { 41 | var outputBuf bytes.Buffer 42 | if err := newCommand(t, 43 | cmd.WithArgs("project", "remove", "mdsbe60td5gwgzetyksdfeyxt4"), 44 | cmd.WithOutput(&outputBuf), 45 | cmd.WithProjectsService(tc.projectsService), 46 | ).Execute(); err != tc.wantError { 47 | t.Fatalf("got error %v, want %v", err, tc.wantError) 48 | } 49 | 50 | gotOutput := outputBuf.String() 51 | if gotOutput != tc.wantOutput { 52 | t.Errorf("got output %q, want %q", gotOutput, tc.wantOutput) 53 | } 54 | }) 55 | t.Run("by name", func(t *testing.T) { 56 | var outputBuf bytes.Buffer 57 | if err := newCommand(t, 58 | cmd.WithArgs("project", "remove", "github", "golang/go"), 59 | cmd.WithOutput(&outputBuf), 60 | cmd.WithProjectsService(tc.projectsService), 61 | ).Execute(); err != tc.wantError { 62 | t.Fatalf("got error %v, want %v", err, tc.wantError) 63 | } 64 | 65 | gotOutput := outputBuf.String() 66 | if gotOutput != tc.wantOutput { 67 | t.Errorf("got output %q, want %q", gotOutput, tc.wantOutput) 68 | } 69 | }) 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /newreleases/cmd/project_search.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "strings" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initProjectSearchCmd(projectCmd *cobra.Command) (err error) { 16 | var optionNameProvider = "provider" 17 | 18 | cmd := &cobra.Command{ 19 | Use: "search NAME", 20 | Short: "Search tracked projects by name", 21 | RunE: func(cmd *cobra.Command, args []string) (err error) { 22 | ctx, cancel := newClientContext(c.config) 23 | defer cancel() 24 | 25 | flags := cmd.Flags() 26 | provider, err := flags.GetString(optionNameProvider) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | if len(args) == 0 { 32 | return cmd.Help() 33 | } 34 | 35 | projects, err := c.projectsService.Search(ctx, strings.Join(args, " "), provider) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | if len(projects) == 0 || err == newreleases.ErrNotFound { 41 | cmd.Println("No projects found.") 42 | return nil 43 | } 44 | 45 | printProjectsTable(cmd, projects) 46 | return nil 47 | }, 48 | PreRunE: func(cmd *cobra.Command, args []string) error { 49 | if err := addClientConfigOptions(cmd, c.config); err != nil { 50 | return err 51 | } 52 | return c.setProjectsService(cmd, args) 53 | }, 54 | } 55 | 56 | cmd.Flags().String(optionNameProvider, "", "filter by provider") 57 | 58 | projectCmd.AddCommand(cmd) 59 | return addClientFlags(cmd) 60 | } 61 | -------------------------------------------------------------------------------- /newreleases/cmd/project_search_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. { 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "testing" 11 | 12 | "newreleases.io/cmd/newreleases/cmd" 13 | "newreleases.io/newreleases" 14 | ) 15 | 16 | func TestProjectCmd_Search(t *testing.T) { 17 | for _, tc := range []struct { 18 | name string 19 | args []string 20 | projectsService cmd.ProjectsService 21 | wantOutput string 22 | wantError error 23 | }{ 24 | { 25 | name: "no projects", 26 | args: []string{"golang"}, 27 | projectsService: newMockProjectsService(1, nil), 28 | wantOutput: "No projects found.\n", 29 | }, 30 | { 31 | name: "minimal project", 32 | args: []string{"golang"}, 33 | projectsService: newMockProjectsService(1, nil, []newreleases.Project{ 34 | {ID: "mdsbe60td5gwgzetyksdfeyxt4", Name: "golang/go", Provider: "github"}, 35 | }), 36 | wantOutput: "ID NAME PROVIDER \nmdsbe60td5gwgzetyksdfeyxt4 golang/go github \n", 37 | }, 38 | { 39 | name: "projects filter by provider", 40 | args: []string{"golang", "--provider", "github"}, 41 | projectsService: newMockProjectsService(1, nil, []newreleases.Project{ 42 | {ID: "mdsbe60td5gwgzetyksdfeyxt4", Name: "golang/go", Provider: "github"}, 43 | {ID: "ksdfeyxt4mdsbe60td5gwgzety", Name: "newreleases/cli-go", Provider: "github"}, 44 | {ID: "gwgzetyksdfeyxt4mdsbe60td5", Name: "vue", Provider: "npm"}, 45 | }), 46 | wantOutput: "ID NAME PROVIDER \nmdsbe60td5gwgzetyksdfeyxt4 golang/go github \nksdfeyxt4mdsbe60td5gwgzety newreleases/cli-go github \n", 47 | }, 48 | { 49 | name: "full project", 50 | args: []string{"golang"}, 51 | projectsService: newMockProjectsService(1, nil, []newreleases.Project{fullProject}), 52 | wantOutput: "ID NAME PROVIDER EMAIL SLACK TELEGRAM DISCORD HANGOUTS CHAT MICROSOFT TEAMS MATTERMOST ROCKET CHAT MATRIX WEBHOOK REGEX EXCLUDE REGEX EXCLUDE INVERSE EXCLUDE PRE-RELEASES EXCLUDE UPDATED NOTE TAGS \nmdsbe60td5gwgzetyksdfeyxt4 golang/go github daily zetyksdfeymdsbe60td5gwgxt4 sbe60td5gwgxtzetyksdfeymd4 tyksdfeymsbegxtzed460td5gw yksdfeymsbe6t0td5gzed4wgxt gwgxtzed4yksdfeymsbe6t0td5 wgxtzed4yksd5dfeymsbe6t0tg 5dfeymsbe6t0tgwgxtzed4yksd 4yksd5e6twgxtzdfeymsbed0tg e6t0td5ykgwgxtzed4eymsbsdf ^0\\.1 ^0\\.3 yes yes Initial no... 33f1db7254b9 \n", 53 | }, 54 | { 55 | name: "error", 56 | args: []string{"golang"}, 57 | projectsService: newMockProjectsService(1, errTest), 58 | wantError: errTest, 59 | }, 60 | } { 61 | t.Run(tc.name, func(t *testing.T) { 62 | var outputBuf bytes.Buffer 63 | if err := newCommand(t, 64 | cmd.WithArgs(append([]string{"project", "search"}, tc.args...)...), 65 | cmd.WithOutput(&outputBuf), 66 | cmd.WithProjectsService(tc.projectsService), 67 | ).Execute(); err != tc.wantError { 68 | t.Fatalf("got error %v, want %v", err, tc.wantError) 69 | } 70 | 71 | wantOutput := trimSpace(tc.wantOutput) 72 | gotOutput := trimSpace(outputBuf.String()) 73 | if gotOutput != wantOutput { 74 | t.Errorf("got output %q, want %q", gotOutput, wantOutput) 75 | } 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /newreleases/cmd/project_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. { 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "context" 10 | "sort" 11 | 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | var ( 16 | minimalProject = newreleases.Project{ 17 | ID: "mdsbe60td5gwgzetyksdfeyxt4", 18 | Name: "golang/go", 19 | Provider: "github", 20 | } 21 | fullProject = newreleases.Project{ 22 | ID: "mdsbe60td5gwgzetyksdfeyxt4", 23 | Name: "golang/go", 24 | Provider: "github", 25 | EmailNotification: newreleases.EmailNotificationDaily, 26 | SlackIDs: []string{"zetyksdfeymdsbe60td5gwgxt4"}, 27 | TelegramChatIDs: []string{"sbe60td5gwgxtzetyksdfeymd4"}, 28 | DiscordIDs: []string{"tyksdfeymsbegxtzed460td5gw"}, 29 | HangoutsChatWebhookIDs: []string{"yksdfeymsbe6t0td5gzed4wgxt"}, 30 | MSTeamsWebhookIDs: []string{"gwgxtzed4yksdfeymsbe6t0td5"}, 31 | MattermostWebhookIDs: []string{"wgxtzed4yksd5dfeymsbe6t0tg"}, 32 | RocketchatWebhookIDs: []string{"5dfeymsbe6t0tgwgxtzed4yksd"}, 33 | MatrixRoomIDs: []string{"4yksd5e6twgxtzdfeymsbed0tg"}, 34 | WebhookIDs: []string{"e6t0td5ykgwgxtzed4eymsbsdf"}, 35 | Exclusions: []newreleases.Exclusion{ 36 | {Value: `^0\.1`}, 37 | {Value: `^0\.3`, Inverse: true}, 38 | }, 39 | ExcludePrereleases: true, 40 | ExcludeUpdated: true, 41 | Note: "Initial note", 42 | TagIDs: []string{"33f1db7254b9"}, 43 | } 44 | ) 45 | 46 | type mockProjectsService struct { 47 | pages [][]newreleases.Project 48 | lastPage int 49 | err error 50 | } 51 | 52 | func newMockProjectsService(lastPage int, err error, pages ...[]newreleases.Project) (s mockProjectsService) { 53 | return mockProjectsService{pages: pages, lastPage: lastPage, err: err} 54 | } 55 | 56 | func (s mockProjectsService) List(ctx context.Context, o newreleases.ProjectListOptions) (projects []newreleases.Project, lastPage int, err error) { 57 | if len(s.pages) == 0 { 58 | return nil, s.lastPage, s.err 59 | } 60 | if o.Provider != "" { 61 | for _, p := range s.pages[o.Page-1] { 62 | if p.Provider == o.Provider { 63 | projects = append(projects, p) 64 | } 65 | } 66 | } else if o.TagID != "" { 67 | for _, page := range s.pages { 68 | for _, p := range page { 69 | for _, tag := range p.TagIDs { 70 | if tag == o.TagID { 71 | projects = append(projects, p) 72 | } 73 | } 74 | } 75 | } 76 | } else { 77 | projects = s.pages[o.Page-1] 78 | } 79 | 80 | switch o.Order { 81 | case newreleases.ProjectListOrderName: 82 | sort.SliceStable(projects, func(i, j int) (less bool) { 83 | return projects[i].Name+projects[i].Provider < projects[j].Name+projects[j].Provider 84 | }) 85 | case newreleases.ProjectListOrderAdded: 86 | sort.SliceStable(projects, func(i, j int) (less bool) { 87 | return projects[i].ID < projects[j].ID 88 | }) 89 | } 90 | if o.Reverse { 91 | sort.Slice(projects, func(i, j int) (less bool) { 92 | return false 93 | }) 94 | } 95 | return projects, s.lastPage, s.err 96 | } 97 | 98 | func (s mockProjectsService) Search(ctx context.Context, query, provider string) (projects []newreleases.Project, err error) { 99 | if len(s.pages) == 0 { 100 | return nil, s.err 101 | } 102 | if provider != "" { 103 | for _, p := range s.pages[0] { 104 | if p.Provider == provider { 105 | projects = append(projects, p) 106 | } 107 | } 108 | } else { 109 | projects = s.pages[0] 110 | } 111 | return projects, s.err 112 | } 113 | 114 | func (s mockProjectsService) GetByID(ctx context.Context, id string) (project *newreleases.Project, err error) { 115 | if len(s.pages) == 0 || len(s.pages[0]) == 0 { 116 | return nil, s.err 117 | } 118 | return &s.pages[0][0], s.err 119 | } 120 | 121 | func (s mockProjectsService) GetByName(ctx context.Context, provider, name string) (project *newreleases.Project, err error) { 122 | if len(s.pages) == 0 || len(s.pages[0]) == 0 { 123 | return nil, s.err 124 | } 125 | return &s.pages[0][0], s.err 126 | } 127 | 128 | func (s mockProjectsService) Add(ctx context.Context, provider, name string, o *newreleases.ProjectOptions) (project *newreleases.Project, err error) { 129 | project = &newreleases.Project{ 130 | ID: "new", 131 | Name: name, 132 | Provider: provider, 133 | URL: "url", 134 | } 135 | if o.EmailNotification != nil { 136 | project.EmailNotification = *o.EmailNotification 137 | } 138 | if o.SlackIDs != nil { 139 | project.SlackIDs = o.SlackIDs 140 | } 141 | if o.TelegramChatIDs != nil { 142 | project.TelegramChatIDs = o.TelegramChatIDs 143 | } 144 | if o.DiscordIDs != nil { 145 | project.DiscordIDs = o.DiscordIDs 146 | } 147 | if o.HangoutsChatWebhookIDs != nil { 148 | project.HangoutsChatWebhookIDs = o.HangoutsChatWebhookIDs 149 | } 150 | if o.MSTeamsWebhookIDs != nil { 151 | project.MSTeamsWebhookIDs = o.MSTeamsWebhookIDs 152 | } 153 | if o.MattermostWebhookIDs != nil { 154 | project.MattermostWebhookIDs = o.MattermostWebhookIDs 155 | } 156 | if o.RocketchatWebhookIDs != nil { 157 | project.RocketchatWebhookIDs = o.RocketchatWebhookIDs 158 | } 159 | if o.MatrixRoomIDs != nil { 160 | project.MatrixRoomIDs = o.MatrixRoomIDs 161 | } 162 | if o.WebhookIDs != nil { 163 | project.WebhookIDs = o.WebhookIDs 164 | } 165 | if o.Exclusions != nil { 166 | project.Exclusions = o.Exclusions 167 | } 168 | if o.ExcludePrereleases != nil { 169 | project.ExcludePrereleases = *o.ExcludePrereleases 170 | } 171 | if o.ExcludeUpdated != nil { 172 | project.ExcludeUpdated = *o.ExcludeUpdated 173 | } 174 | if o.Note != nil { 175 | project.Note = *o.Note 176 | } 177 | if o.TagIDs != nil { 178 | project.TagIDs = o.TagIDs 179 | } 180 | return project, s.err 181 | } 182 | 183 | func (s mockProjectsService) UpdateByID(ctx context.Context, id string, o *newreleases.ProjectOptions) (project *newreleases.Project, err error) { 184 | if len(s.pages) == 0 || len(s.pages[0]) == 0 { 185 | return nil, s.err 186 | } 187 | project = &s.pages[0][0] 188 | if o.EmailNotification != nil { 189 | project.EmailNotification = *o.EmailNotification 190 | } 191 | if o.SlackIDs != nil { 192 | project.SlackIDs = o.SlackIDs 193 | } 194 | if o.TelegramChatIDs != nil { 195 | project.TelegramChatIDs = o.TelegramChatIDs 196 | } 197 | if o.DiscordIDs != nil { 198 | project.DiscordIDs = o.DiscordIDs 199 | } 200 | if o.HangoutsChatWebhookIDs != nil { 201 | project.HangoutsChatWebhookIDs = o.HangoutsChatWebhookIDs 202 | } 203 | if o.MSTeamsWebhookIDs != nil { 204 | project.MSTeamsWebhookIDs = o.MSTeamsWebhookIDs 205 | } 206 | if o.MattermostWebhookIDs != nil { 207 | project.MattermostWebhookIDs = o.MattermostWebhookIDs 208 | } 209 | if o.RocketchatWebhookIDs != nil { 210 | project.RocketchatWebhookIDs = o.RocketchatWebhookIDs 211 | } 212 | if o.MatrixRoomIDs != nil { 213 | project.MatrixRoomIDs = o.MatrixRoomIDs 214 | } 215 | if o.WebhookIDs != nil { 216 | project.WebhookIDs = o.WebhookIDs 217 | } 218 | if o.Exclusions != nil { 219 | project.Exclusions = o.Exclusions 220 | } 221 | if o.ExcludePrereleases != nil { 222 | project.ExcludePrereleases = *o.ExcludePrereleases 223 | } 224 | if o.ExcludeUpdated != nil { 225 | project.ExcludeUpdated = *o.ExcludeUpdated 226 | } 227 | if o.Note != nil { 228 | project.Note = *o.Note 229 | } 230 | if o.TagIDs != nil { 231 | project.TagIDs = o.TagIDs 232 | } 233 | return project, s.err 234 | } 235 | 236 | func (s mockProjectsService) UpdateByName(ctx context.Context, provider, name string, o *newreleases.ProjectOptions) (project *newreleases.Project, err error) { 237 | if len(s.pages) == 0 || len(s.pages[0]) == 0 { 238 | return nil, s.err 239 | } 240 | project = &s.pages[0][0] 241 | if o.EmailNotification != nil { 242 | project.EmailNotification = *o.EmailNotification 243 | } 244 | if o.SlackIDs != nil { 245 | project.SlackIDs = o.SlackIDs 246 | } 247 | if o.TelegramChatIDs != nil { 248 | project.TelegramChatIDs = o.TelegramChatIDs 249 | } 250 | if o.DiscordIDs != nil { 251 | project.DiscordIDs = o.DiscordIDs 252 | } 253 | if o.HangoutsChatWebhookIDs != nil { 254 | project.HangoutsChatWebhookIDs = o.HangoutsChatWebhookIDs 255 | } 256 | if o.MSTeamsWebhookIDs != nil { 257 | project.MSTeamsWebhookIDs = o.MSTeamsWebhookIDs 258 | } 259 | if o.MattermostWebhookIDs != nil { 260 | project.MattermostWebhookIDs = o.MattermostWebhookIDs 261 | } 262 | if o.RocketchatWebhookIDs != nil { 263 | project.RocketchatWebhookIDs = o.RocketchatWebhookIDs 264 | } 265 | if o.MatrixRoomIDs != nil { 266 | project.MatrixRoomIDs = o.MatrixRoomIDs 267 | } 268 | if o.WebhookIDs != nil { 269 | project.WebhookIDs = o.WebhookIDs 270 | } 271 | if o.Exclusions != nil { 272 | project.Exclusions = o.Exclusions 273 | } 274 | if o.ExcludePrereleases != nil { 275 | project.ExcludePrereleases = *o.ExcludePrereleases 276 | } 277 | if o.ExcludeUpdated != nil { 278 | project.ExcludeUpdated = *o.ExcludeUpdated 279 | } 280 | if o.Note != nil { 281 | project.Note = *o.Note 282 | } 283 | if o.TagIDs != nil { 284 | project.TagIDs = o.TagIDs 285 | } 286 | return project, s.err 287 | } 288 | 289 | func (s mockProjectsService) DeleteByID(ctx context.Context, id string) (err error) { 290 | return s.err 291 | } 292 | 293 | func (s mockProjectsService) DeleteByName(ctx context.Context, provider, name string) (err error) { 294 | return s.err 295 | } 296 | -------------------------------------------------------------------------------- /newreleases/cmd/project_update.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "strings" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initProjectUpdateCmd(projectCmd *cobra.Command) (err error) { 16 | var ( 17 | optionNameEmail = "email" 18 | optionNameSlack = "slack" 19 | optionNameSlackRemove = "slack-remove" 20 | optionNameTelegram = "telegram" 21 | optionNameTelegramRemove = "telegram-remove" 22 | optionNameDiscord = "discord" 23 | optionNameDiscordRemove = "discord-remove" 24 | optionNameHangoutsChat = "hangouts-chat" 25 | optionNameHangoutsChatRemove = "hangouts-chat-remove" 26 | optionNameMicrosoftTeams = "microsoft-teams" 27 | optionNameMicrosoftTeamsRemove = "microsoft-teams-remove" 28 | optionNameMattermost = "mattermost" 29 | optionNameMattermostRemove = "mattermost-remove" 30 | optionNameRocketchat = "rocketchat" 31 | optionNameRocketchatRemove = "rocketchat-remove" 32 | optionNameMatrix = "matrix" 33 | optionNameMatrixRemove = "matrix-remove" 34 | optionNameWebhook = "webhook" 35 | optionNameWebhookRemove = "webhook-remove" 36 | optionNameExclusions = "regex-exclude" 37 | optionNameExclusionsRemove = "regex-exclude-remove" 38 | optionNameExcludePrereleases = "exclude-prereleases" 39 | optionNameExcludeUpdated = "exclude-updated" 40 | optionNameNote = "note" 41 | optionNameTag = "tag" 42 | optionNameTagRemove = "tag-remove" 43 | ) 44 | 45 | cmd := &cobra.Command{ 46 | Use: "update [PROVIDER PROJECT_NAME] | [PROJECT_ID]", 47 | Short: "Update a tracked project", 48 | RunE: func(cmd *cobra.Command, args []string) (err error) { 49 | ctx, cancel := newClientContext(c.config) 50 | defer cancel() 51 | 52 | o := &newreleases.ProjectOptions{} 53 | 54 | flags := cmd.Flags() 55 | email, err := flags.GetString(optionNameEmail) 56 | if err != nil { 57 | return err 58 | } 59 | if email != "" { 60 | e := newreleases.EmailNotification(email) 61 | o.EmailNotification = &e 62 | } 63 | slackRemove, err := flags.GetBool(optionNameSlackRemove) 64 | if err != nil { 65 | return err 66 | } 67 | if slackRemove { 68 | o.SlackIDs = make([]string, 0) 69 | } else { 70 | slackIDs, err := flags.GetStringArray(optionNameSlack) 71 | if err != nil { 72 | return err 73 | } 74 | if len(slackIDs) > 0 { 75 | o.SlackIDs = slackIDs 76 | } 77 | } 78 | telegramRemove, err := flags.GetBool(optionNameTelegramRemove) 79 | if err != nil { 80 | return err 81 | } 82 | if telegramRemove { 83 | o.TelegramChatIDs = make([]string, 0) 84 | } else { 85 | telegramChatIDs, err := flags.GetStringArray(optionNameTelegram) 86 | if err != nil { 87 | return err 88 | } 89 | if len(telegramChatIDs) > 0 { 90 | o.TelegramChatIDs = telegramChatIDs 91 | } 92 | } 93 | discordRemove, err := flags.GetBool(optionNameDiscordRemove) 94 | if err != nil { 95 | return err 96 | } 97 | if discordRemove { 98 | o.DiscordIDs = make([]string, 0) 99 | } else { 100 | discordIDs, err := flags.GetStringArray(optionNameDiscord) 101 | if err != nil { 102 | return err 103 | } 104 | if len(discordIDs) > 0 { 105 | o.DiscordIDs = discordIDs 106 | } 107 | } 108 | hangoutsChatRemove, err := flags.GetBool(optionNameHangoutsChatRemove) 109 | if err != nil { 110 | return err 111 | } 112 | if hangoutsChatRemove { 113 | o.HangoutsChatWebhookIDs = make([]string, 0) 114 | } else { 115 | hangoutsChatWebhookIDs, err := flags.GetStringArray(optionNameHangoutsChat) 116 | if err != nil { 117 | return err 118 | } 119 | if len(hangoutsChatWebhookIDs) > 0 { 120 | o.HangoutsChatWebhookIDs = hangoutsChatWebhookIDs 121 | } 122 | } 123 | microsoftTeamsRemove, err := flags.GetBool(optionNameMicrosoftTeamsRemove) 124 | if err != nil { 125 | return err 126 | } 127 | if microsoftTeamsRemove { 128 | o.MSTeamsWebhookIDs = make([]string, 0) 129 | } else { 130 | msTeamsWebhookIDs, err := flags.GetStringArray(optionNameMicrosoftTeams) 131 | if err != nil { 132 | return err 133 | } 134 | if len(msTeamsWebhookIDs) > 0 { 135 | o.MSTeamsWebhookIDs = msTeamsWebhookIDs 136 | } 137 | } 138 | mattermostRemove, err := flags.GetBool(optionNameMattermostRemove) 139 | if err != nil { 140 | return err 141 | } 142 | if mattermostRemove { 143 | o.MattermostWebhookIDs = make([]string, 0) 144 | } else { 145 | mattermostWebhookIDs, err := flags.GetStringArray(optionNameMattermost) 146 | if err != nil { 147 | return err 148 | } 149 | if len(mattermostWebhookIDs) > 0 { 150 | o.MattermostWebhookIDs = mattermostWebhookIDs 151 | } 152 | } 153 | rocketchatRemove, err := flags.GetBool(optionNameRocketchatRemove) 154 | if err != nil { 155 | return err 156 | } 157 | if rocketchatRemove { 158 | o.RocketchatWebhookIDs = make([]string, 0) 159 | } else { 160 | rocketchatWebhookIDs, err := flags.GetStringArray(optionNameRocketchat) 161 | if err != nil { 162 | return err 163 | } 164 | if len(rocketchatWebhookIDs) > 0 { 165 | o.RocketchatWebhookIDs = rocketchatWebhookIDs 166 | } 167 | } 168 | matrixRemove, err := flags.GetBool(optionNameMatrixRemove) 169 | if err != nil { 170 | return err 171 | } 172 | if matrixRemove { 173 | o.MatrixRoomIDs = make([]string, 0) 174 | } else { 175 | matrixRoomIDs, err := flags.GetStringArray(optionNameMatrix) 176 | if err != nil { 177 | return err 178 | } 179 | if len(matrixRoomIDs) > 0 { 180 | o.MatrixRoomIDs = matrixRoomIDs 181 | } 182 | } 183 | webhookRemove, err := flags.GetBool(optionNameWebhookRemove) 184 | if err != nil { 185 | return err 186 | } 187 | if webhookRemove { 188 | o.WebhookIDs = make([]string, 0) 189 | } else { 190 | webhookIDs, err := flags.GetStringArray(optionNameWebhook) 191 | if err != nil { 192 | return err 193 | } 194 | if len(webhookIDs) > 0 { 195 | o.WebhookIDs = webhookIDs 196 | } 197 | } 198 | exclusionsRemove, err := flags.GetBool(optionNameExclusionsRemove) 199 | if err != nil { 200 | return err 201 | } 202 | if exclusionsRemove { 203 | o.Exclusions = make([]newreleases.Exclusion, 0) 204 | } else { 205 | exclusions, err := flags.GetStringArray(optionNameExclusions) 206 | if err != nil { 207 | return err 208 | } 209 | for _, v := range exclusions { 210 | var inverse bool 211 | if strings.HasSuffix(v, "-inverse") { 212 | inverse = true 213 | v = strings.TrimSuffix(v, "-inverse") 214 | } 215 | o.Exclusions = append(o.Exclusions, newreleases.Exclusion{ 216 | Value: v, 217 | Inverse: inverse, 218 | }) 219 | } 220 | } 221 | if flags.Changed(optionNameExcludePrereleases) { 222 | excludePrereleases, err := flags.GetBool(optionNameExcludePrereleases) 223 | if err != nil { 224 | return err 225 | } 226 | o.ExcludePrereleases = &excludePrereleases 227 | } 228 | if flags.Changed(optionNameExcludeUpdated) { 229 | excludeUpdated, err := flags.GetBool(optionNameExcludeUpdated) 230 | if err != nil { 231 | return err 232 | } 233 | o.ExcludeUpdated = &excludeUpdated 234 | } 235 | 236 | tagRemove, err := flags.GetBool(optionNameTagRemove) 237 | if err != nil { 238 | return err 239 | } 240 | if tagRemove { 241 | o.TagIDs = make([]string, 0) 242 | } else { 243 | tagIDs, err := flags.GetStringArray(optionNameTag) 244 | if err != nil { 245 | return err 246 | } 247 | if len(tagIDs) > 0 { 248 | o.TagIDs = tagIDs 249 | } 250 | } 251 | 252 | if flags.Changed(optionNameNote) { 253 | note, err := flags.GetString(optionNameNote) 254 | if err != nil { 255 | return err 256 | } 257 | o.Note = ¬e 258 | } 259 | 260 | var project *newreleases.Project 261 | switch len(args) { 262 | case 1: 263 | project, err = c.projectsService.UpdateByID(ctx, args[0], o) 264 | case 2: 265 | project, err = c.projectsService.UpdateByName(ctx, args[0], args[1], o) 266 | default: 267 | return cmd.Help() 268 | } 269 | if err != nil { 270 | return err 271 | } 272 | 273 | if project == nil || err == newreleases.ErrNotFound { 274 | cmd.Println("Project not found.") 275 | return nil 276 | } 277 | 278 | printProject(cmd, project) 279 | return nil 280 | }, 281 | PreRunE: func(cmd *cobra.Command, args []string) error { 282 | if err := addClientConfigOptions(cmd, c.config); err != nil { 283 | return err 284 | } 285 | return c.setProjectsService(cmd, args) 286 | }, 287 | } 288 | 289 | cmd.Flags().String(optionNameEmail, "", "frequency of email notifications: instant, hourly, daily, weekly, none") 290 | cmd.Flags().StringArray(optionNameSlack, nil, "Slack channel ID") 291 | cmd.Flags().Bool(optionNameSlackRemove, false, "remove Slack notifications") 292 | cmd.Flags().StringArray(optionNameTelegram, nil, "Telegram chat ID") 293 | cmd.Flags().Bool(optionNameTelegramRemove, false, "remove Telegram notifications") 294 | cmd.Flags().StringArray(optionNameDiscord, nil, "Discord channel ID") 295 | cmd.Flags().Bool(optionNameDiscordRemove, false, "remove Discord notifications") 296 | cmd.Flags().StringArray(optionNameHangoutsChat, nil, "Hangouts Chat webhook ID") 297 | cmd.Flags().Bool(optionNameHangoutsChatRemove, false, "remove Hangouts Chat notifications") 298 | cmd.Flags().StringArray(optionNameMicrosoftTeams, nil, "Microsoft Teams webhook ID") 299 | cmd.Flags().Bool(optionNameMicrosoftTeamsRemove, false, "remove Microsoft Teams notifications") 300 | cmd.Flags().StringArray(optionNameMattermost, nil, "Mattermost webhook ID") 301 | cmd.Flags().Bool(optionNameMattermostRemove, false, "remove Mattermost notifications") 302 | cmd.Flags().StringArray(optionNameRocketchat, nil, "Rocket.Chat webhook ID") 303 | cmd.Flags().Bool(optionNameRocketchatRemove, false, "remove Rocket.Chat notifications") 304 | cmd.Flags().StringArray(optionNameMatrix, nil, "Matrix room ID") 305 | cmd.Flags().Bool(optionNameMatrixRemove, false, "remove Matrix notifications") 306 | cmd.Flags().StringArray(optionNameWebhook, nil, "Webhook ID") 307 | cmd.Flags().Bool(optionNameWebhookRemove, false, "remove Webhook notifications") 308 | cmd.Flags().StringArray(optionNameExclusions, nil, "Regex version exclusion, suffix with \"-inverse\" for inclusion") 309 | cmd.Flags().Bool(optionNameExclusionsRemove, false, "remove Regex version exclusions") 310 | cmd.Flags().Bool(optionNameExcludePrereleases, false, "exclude pre-releases") 311 | cmd.Flags().Bool(optionNameExcludeUpdated, false, "exclude updated") 312 | cmd.Flags().StringArray(optionNameTag, nil, "Tag ID") 313 | cmd.Flags().Bool(optionNameTagRemove, false, "remove Tags") 314 | cmd.Flags().String(optionNameNote, "", "Note") 315 | 316 | projectCmd.AddCommand(cmd) 317 | return addClientFlags(cmd) 318 | } 319 | -------------------------------------------------------------------------------- /newreleases/cmd/providers.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func (c *command) initProviderCmd() (err error) { 15 | optionNameAdded := "added" 16 | 17 | cmd := &cobra.Command{ 18 | Use: "providers", 19 | Aliases: []string{"provider"}, 20 | Short: "Get project providers", 21 | RunE: func(cmd *cobra.Command, args []string) (err error) { 22 | ctx, cancel := newClientContext(c.config) 23 | defer cancel() 24 | 25 | added, err := cmd.Flags().GetBool(optionNameAdded) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | var providers []string 31 | if added { 32 | providers, err = c.providersService.ListAdded(ctx) 33 | } else { 34 | providers, err = c.providersService.List(ctx) 35 | } 36 | if err != nil { 37 | return err 38 | } 39 | 40 | if len(providers) == 0 { 41 | cmd.Println("No providers found.") 42 | return nil 43 | } 44 | 45 | printProvidersTable(cmd, providers) 46 | 47 | return nil 48 | }, 49 | PreRunE: func(cmd *cobra.Command, args []string) error { 50 | if err := addClientConfigOptions(cmd, c.config); err != nil { 51 | return err 52 | } 53 | return c.setProvidersService(cmd, args) 54 | }, 55 | } 56 | 57 | cmd.Flags().Bool(optionNameAdded, false, "get only providers for projects that are added for tracking") 58 | 59 | c.root.AddCommand(cmd) 60 | return addClientFlags(cmd) 61 | } 62 | 63 | func (c *command) setProvidersService(cmd *cobra.Command, args []string) (err error) { 64 | if c.providersService != nil { 65 | return nil 66 | } 67 | client, err := c.getClient(cmd) 68 | if err != nil { 69 | return err 70 | } 71 | c.providersService = client.Providers 72 | return nil 73 | } 74 | 75 | type providersService interface { 76 | List(ctx context.Context) (providers []string, err error) 77 | ListAdded(ctx context.Context) (providers []string, err error) 78 | } 79 | 80 | func printProvidersTable(cmd *cobra.Command, providers []string) { 81 | table := newTable(cmd.OutOrStdout()) 82 | table.SetHeader([]string{"ID"}) 83 | for _, id := range providers { 84 | table.Append([]string{id}) 85 | } 86 | table.Render() 87 | } 88 | -------------------------------------------------------------------------------- /newreleases/cmd/providers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "context" 11 | "testing" 12 | 13 | "newreleases.io/cmd/newreleases/cmd" 14 | ) 15 | 16 | func TestProviderCmd(t *testing.T) { 17 | for _, tc := range []struct { 18 | name string 19 | providersService cmd.ProvidersService 20 | added bool 21 | wantOutput string 22 | wantError error 23 | }{ 24 | { 25 | name: "no providers", 26 | providersService: newMockProvidersService(nil, nil, nil), 27 | wantOutput: "No providers found.\n", 28 | }, 29 | { 30 | name: "no added providers", 31 | added: true, 32 | providersService: newMockProvidersService([]string{"github", "pypi", "npm"}, nil, nil), 33 | wantOutput: "No providers found.\n", 34 | }, 35 | { 36 | name: "providers", 37 | providersService: newMockProvidersService([]string{"github", "pypi", "cargo", "dockerhub"}, []string{"github", "pypi"}, nil), 38 | wantOutput: "ID \ngithub \npypi \ncargo \ndockerhub \n", 39 | }, 40 | { 41 | name: "added providers", 42 | added: true, 43 | providersService: newMockProvidersService([]string{"github", "pypi", "yarn", "dockerhub"}, []string{"github", "pypi"}, nil), 44 | wantOutput: "ID \ngithub \npypi \n", 45 | }, 46 | { 47 | name: "error", 48 | providersService: newMockProvidersService(nil, nil, errTest), 49 | wantError: errTest, 50 | }, 51 | } { 52 | t.Run(tc.name, func(t *testing.T) { 53 | for _, alias := range []string{"providers", "provider"} { 54 | args := []string{alias} 55 | if tc.added { 56 | args = append(args, "--added") 57 | } 58 | var outputBuf bytes.Buffer 59 | if err := newCommand(t, 60 | cmd.WithArgs(args...), 61 | cmd.WithOutput(&outputBuf), 62 | cmd.WithProvidersService(tc.providersService), 63 | ).Execute(); err != tc.wantError { 64 | t.Fatalf("got error %v, want %v", err, tc.wantError) 65 | } 66 | 67 | gotOutput := outputBuf.String() 68 | if gotOutput != tc.wantOutput { 69 | t.Errorf("got output %q, want %q", gotOutput, tc.wantOutput) 70 | } 71 | } 72 | }) 73 | } 74 | } 75 | 76 | type mockProvidersService struct { 77 | providers []string 78 | addedProviders []string 79 | err error 80 | } 81 | 82 | func newMockProvidersService(providers, addedProviders []string, err error) (s mockProvidersService) { 83 | return mockProvidersService{providers: providers, addedProviders: addedProviders, err: err} 84 | } 85 | 86 | func (s mockProvidersService) List(ctx context.Context) (providers []string, err error) { 87 | return s.providers, s.err 88 | } 89 | 90 | func (s mockProvidersService) ListAdded(ctx context.Context) (providers []string, err error) { 91 | return s.addedProviders, s.err 92 | } 93 | -------------------------------------------------------------------------------- /newreleases/cmd/release.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | "strings" 11 | 12 | "github.com/spf13/cobra" 13 | "jaytaylor.com/html2text" 14 | "newreleases.io/newreleases" 15 | ) 16 | 17 | func (c *command) initReleaseCmd() (err error) { 18 | cmd := &cobra.Command{ 19 | Use: "release", 20 | Short: "Get project releases and release notes", 21 | } 22 | 23 | if err := c.initReleaseListCmd(cmd); err != nil { 24 | return err 25 | } 26 | if err := c.initReleaseGetCmd(cmd); err != nil { 27 | return err 28 | } 29 | if err := c.initReleaseGetLatestCmd(cmd); err != nil { 30 | return err 31 | } 32 | if err := c.initReleaseNoteCmd(cmd); err != nil { 33 | return err 34 | } 35 | 36 | c.root.AddCommand(cmd) 37 | return nil 38 | } 39 | 40 | func (c *command) initReleaseListCmd(releaseCmd *cobra.Command) (err error) { 41 | optionNamePage := "page" 42 | 43 | cmd := &cobra.Command{ 44 | Use: "list [PROVIDER PROJECT_NAME] | [PROJECT_ID]", 45 | Short: "Get project releases", 46 | RunE: func(cmd *cobra.Command, args []string) (err error) { 47 | ctx, cancel := newClientContext(c.config) 48 | defer cancel() 49 | 50 | page, err := cmd.Flags().GetInt(optionNamePage) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | var releases []newreleases.Release 56 | var lastPage int 57 | switch len(args) { 58 | case 1: 59 | releases, lastPage, err = c.releasesService.ListByProjectID(ctx, args[0], page) 60 | case 2: 61 | releases, lastPage, err = c.releasesService.ListByProjectName(ctx, args[0], args[1], page) 62 | default: 63 | return cmd.Help() 64 | } 65 | if err != nil { 66 | return err 67 | } 68 | 69 | if len(releases) == 0 || err == newreleases.ErrNotFound { 70 | if page <= 1 { 71 | cmd.Println("No releases found.") 72 | return nil 73 | } 74 | cmd.Printf("No releases found on page %v.\n", page) 75 | return nil 76 | } 77 | 78 | printReleasesTable(cmd, releases) 79 | 80 | if page < lastPage { 81 | cmd.Println("More releases on the next page...") 82 | } 83 | 84 | return nil 85 | }, 86 | PreRunE: func(cmd *cobra.Command, args []string) error { 87 | if err := addClientConfigOptions(cmd, c.config); err != nil { 88 | return err 89 | } 90 | return c.setReleasesService(cmd, args) 91 | }, 92 | } 93 | 94 | cmd.Flags().IntP(optionNamePage, "p", 1, "page number") 95 | 96 | releaseCmd.AddCommand(cmd) 97 | return addClientFlags(cmd) 98 | } 99 | 100 | func (c *command) initReleaseGetCmd(releaseCmd *cobra.Command) (err error) { 101 | cmd := &cobra.Command{ 102 | Use: "get [provider project_name] | [project_id] version", 103 | Short: "Get a specific project release", 104 | RunE: func(cmd *cobra.Command, args []string) (err error) { 105 | ctx, cancel := newClientContext(c.config) 106 | defer cancel() 107 | 108 | var release *newreleases.Release 109 | switch len(args) { 110 | case 2: 111 | release, err = c.releasesService.GetByProjectID(ctx, args[0], args[1]) 112 | case 3: 113 | release, err = c.releasesService.GetByProjectName(ctx, args[0], args[1], args[2]) 114 | default: 115 | return cmd.Help() 116 | } 117 | if err != nil { 118 | return err 119 | } 120 | 121 | if release == nil { 122 | cmd.Println("Release not found.") 123 | return nil 124 | } 125 | 126 | printRelease(cmd, release) 127 | return nil 128 | }, 129 | PreRunE: func(cmd *cobra.Command, args []string) error { 130 | if err := addClientConfigOptions(cmd, c.config); err != nil { 131 | return err 132 | } 133 | return c.setReleasesService(cmd, args) 134 | }, 135 | } 136 | 137 | releaseCmd.AddCommand(cmd) 138 | return addClientFlags(cmd) 139 | } 140 | 141 | func (c *command) initReleaseGetLatestCmd(releaseCmd *cobra.Command) (err error) { 142 | cmd := &cobra.Command{ 143 | Use: "get-latest [provider project_name] | [project_id]", 144 | Short: "Get the latest non-excluded project release", 145 | RunE: func(cmd *cobra.Command, args []string) (err error) { 146 | ctx, cancel := newClientContext(c.config) 147 | defer cancel() 148 | 149 | var release *newreleases.Release 150 | switch len(args) { 151 | case 1: 152 | release, err = c.releasesService.GetLatestByProjectID(ctx, args[0]) 153 | case 2: 154 | release, err = c.releasesService.GetLatestByProjectName(ctx, args[0], args[1]) 155 | default: 156 | return cmd.Help() 157 | } 158 | if err != nil { 159 | return err 160 | } 161 | 162 | if release == nil { 163 | cmd.Println("Release not found.") 164 | return nil 165 | } 166 | 167 | printRelease(cmd, release) 168 | return nil 169 | }, 170 | PreRunE: func(cmd *cobra.Command, args []string) error { 171 | if err := addClientConfigOptions(cmd, c.config); err != nil { 172 | return err 173 | } 174 | return c.setReleasesService(cmd, args) 175 | }, 176 | } 177 | 178 | releaseCmd.AddCommand(cmd) 179 | return addClientFlags(cmd) 180 | } 181 | 182 | func (c *command) initReleaseNoteCmd(releaseCmd *cobra.Command) (err error) { 183 | cmd := &cobra.Command{ 184 | Use: "note [PROVIDER PROJECT_NAME] | [PROJECT_ID] version", 185 | Short: "Get a project release note", 186 | RunE: func(cmd *cobra.Command, args []string) (err error) { 187 | ctx, cancel := newClientContext(c.config) 188 | defer cancel() 189 | 190 | var releaseNote *newreleases.ReleaseNote 191 | switch len(args) { 192 | case 2: 193 | releaseNote, err = c.releasesService.GetNoteByProjectID(ctx, args[0], args[1]) 194 | case 3: 195 | releaseNote, err = c.releasesService.GetNoteByProjectName(ctx, args[0], args[1], args[2]) 196 | default: 197 | return cmd.Help() 198 | } 199 | if err != nil { 200 | return err 201 | } 202 | 203 | if releaseNote == nil { 204 | cmd.Println("Release note not found.") 205 | return nil 206 | } 207 | 208 | printReleaseNote(cmd, releaseNote) 209 | return nil 210 | }, 211 | PreRunE: func(cmd *cobra.Command, args []string) error { 212 | if err := addClientConfigOptions(cmd, c.config); err != nil { 213 | return err 214 | } 215 | return c.setReleasesService(cmd, args) 216 | }, 217 | } 218 | 219 | releaseCmd.AddCommand(cmd) 220 | return addClientFlags(cmd) 221 | } 222 | 223 | func (c *command) setReleasesService(cmd *cobra.Command, args []string) (err error) { 224 | if c.releasesService != nil { 225 | return nil 226 | } 227 | client, err := c.getClient(cmd) 228 | if err != nil { 229 | return err 230 | } 231 | c.releasesService = client.Releases 232 | return nil 233 | } 234 | 235 | type releasesService interface { 236 | ListByProjectID(ctx context.Context, projectID string, page int) (releases []newreleases.Release, lastPage int, err error) 237 | ListByProjectName(ctx context.Context, provider, projectName string, page int) (releases []newreleases.Release, lastPage int, err error) 238 | GetByProjectID(ctx context.Context, projectID, version string) (release *newreleases.Release, err error) 239 | GetByProjectName(ctx context.Context, provider, projectName, version string) (release *newreleases.Release, err error) 240 | GetLatestByProjectID(ctx context.Context, projectID string) (release *newreleases.Release, err error) 241 | GetLatestByProjectName(ctx context.Context, provider, projectName string) (release *newreleases.Release, err error) 242 | GetNoteByProjectID(ctx context.Context, projectID string, version string) (release *newreleases.ReleaseNote, err error) 243 | GetNoteByProjectName(ctx context.Context, provider, projectName string, version string) (release *newreleases.ReleaseNote, err error) 244 | } 245 | 246 | func printReleasesTable(cmd *cobra.Command, releases []newreleases.Release) { 247 | table := newTable(cmd.OutOrStdout()) 248 | table.SetHeader([]string{"Version", "Date", "Pre-Release", "Has Note", "Updated", "Excluded", "CVE"}) 249 | for _, r := range releases { 250 | table.Append([]string{r.Version, r.Date.Local().String(), yesNo(r.IsPrerelease), yesNo(r.HasNote), yesNo(r.IsUpdated), yesNo(r.IsExcluded), yesNo(len(r.CVE) > 0)}) 251 | } 252 | table.Render() 253 | } 254 | 255 | func printRelease(cmd *cobra.Command, r *newreleases.Release) { 256 | table := newTable(cmd.OutOrStdout()) 257 | table.Append([]string{"Version:", r.Version}) 258 | table.Append([]string{"Date:", r.Date.Local().String()}) 259 | 260 | if r.IsPrerelease { 261 | table.Append([]string{"Pre-Release:", "yes"}) 262 | } 263 | if r.HasNote { 264 | table.Append([]string{"Has Note:", "yes"}) 265 | } 266 | if r.IsUpdated { 267 | table.Append([]string{"Updated:", "yes"}) 268 | } 269 | if r.IsExcluded { 270 | table.Append([]string{"Excluded:", "yes"}) 271 | } 272 | if len(r.CVE) > 0 { 273 | table.Append([]string{"CVE:", strings.Join(r.CVE, ", ")}) 274 | } 275 | table.Render() 276 | } 277 | 278 | func printReleaseNote(cmd *cobra.Command, n *newreleases.ReleaseNote) { 279 | if n.Title != "" { 280 | cmd.Println(strings.TrimSpace(n.Title)) 281 | cmd.Println() 282 | } 283 | if n.Message != "" { 284 | message, err := html2text.FromString(n.Message, html2text.Options{PrettyTables: true}) 285 | if err != nil { 286 | panic(err) 287 | } 288 | cmd.Println(strings.TrimSpace(message)) 289 | cmd.Println() 290 | } 291 | if n.URL != "" { 292 | cmd.Println(strings.TrimSpace(n.URL)) 293 | cmd.Println() 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /newreleases/cmd/rocketchat.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initRocketchatCmd() (err error) { 16 | cmd := &cobra.Command{ 17 | Use: "rocketchat", 18 | Short: "List Rocket.Chat integrations", 19 | RunE: func(cmd *cobra.Command, args []string) (err error) { 20 | ctx, cancel := newClientContext(c.config) 21 | defer cancel() 22 | 23 | webhooks, err := c.rocketchatWebhooksService.List(ctx) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | if len(webhooks) == 0 { 29 | cmd.Println("No Rocket.Chat Webhooks found.") 30 | return nil 31 | } 32 | 33 | printWebhooksTable(cmd, webhooks) 34 | 35 | return nil 36 | }, 37 | PreRunE: func(cmd *cobra.Command, args []string) error { 38 | if err := addClientConfigOptions(cmd, c.config); err != nil { 39 | return err 40 | } 41 | return c.setRocketchatWebhooksService(cmd, args) 42 | }, 43 | } 44 | 45 | c.root.AddCommand(cmd) 46 | return addClientFlags(cmd) 47 | } 48 | 49 | func (c *command) setRocketchatWebhooksService(cmd *cobra.Command, args []string) (err error) { 50 | if c.rocketchatWebhooksService != nil { 51 | return nil 52 | } 53 | client, err := c.getClient(cmd) 54 | if err != nil { 55 | return err 56 | } 57 | c.rocketchatWebhooksService = client.RocketchatWebhooks 58 | return nil 59 | } 60 | 61 | type rocketchatWebhooksService interface { 62 | List(ctx context.Context) (webhooks []newreleases.Webhook, err error) 63 | } 64 | -------------------------------------------------------------------------------- /newreleases/cmd/rocketchat_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "context" 11 | "testing" 12 | 13 | "newreleases.io/cmd/newreleases/cmd" 14 | "newreleases.io/newreleases" 15 | ) 16 | 17 | func TestRocketchatCmd(t *testing.T) { 18 | for _, tc := range []struct { 19 | name string 20 | rocketchatWebhooksService cmd.RocketchatWebhooksService 21 | wantOutput string 22 | wantError error 23 | }{ 24 | { 25 | name: "no webhooks", 26 | rocketchatWebhooksService: newMockRocketchatWebhooksService(nil, nil), 27 | wantOutput: "No Rocket.Chat Webhooks found.\n", 28 | }, 29 | { 30 | name: "with webhooks", 31 | rocketchatWebhooksService: newMockRocketchatWebhooksService([]newreleases.Webhook{ 32 | { 33 | ID: "abOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1", 34 | Name: "NewReleases", 35 | }, 36 | { 37 | ID: "f1anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1", 38 | Name: "Awesome project", 39 | }, 40 | }, nil), 41 | wantOutput: "ID NAME \nabOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1 NewReleases \nf1anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1 Awesome project \n", 42 | }, 43 | { 44 | name: "error", 45 | rocketchatWebhooksService: newMockRocketchatWebhooksService(nil, errTest), 46 | wantError: errTest, 47 | }, 48 | } { 49 | t.Run(tc.name, func(t *testing.T) { 50 | var outputBuf bytes.Buffer 51 | if err := newCommand(t, 52 | cmd.WithArgs("rocketchat"), 53 | cmd.WithOutput(&outputBuf), 54 | cmd.WithRocketchatWebhooksService(tc.rocketchatWebhooksService), 55 | ).Execute(); err != tc.wantError { 56 | t.Fatalf("got error %v, want %v", err, tc.wantError) 57 | } 58 | 59 | gotOutput := outputBuf.String() 60 | if gotOutput != tc.wantOutput { 61 | t.Errorf("got output %q, want %q", gotOutput, tc.wantOutput) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | type mockRocketchatWebhooksService struct { 68 | webhooks []newreleases.Webhook 69 | err error 70 | } 71 | 72 | func newMockRocketchatWebhooksService(webhooks []newreleases.Webhook, err error) (s mockRocketchatWebhooksService) { 73 | return mockRocketchatWebhooksService{webhooks: webhooks, err: err} 74 | } 75 | 76 | func (s mockRocketchatWebhooksService) List(ctx context.Context) (webhooks []newreleases.Webhook, err error) { 77 | return s.webhooks, s.err 78 | } 79 | -------------------------------------------------------------------------------- /newreleases/cmd/slack.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initSlackCmd() (err error) { 16 | cmd := &cobra.Command{ 17 | Use: "slack", 18 | Short: "List Slack integrations", 19 | RunE: func(cmd *cobra.Command, args []string) (err error) { 20 | ctx, cancel := newClientContext(c.config) 21 | defer cancel() 22 | 23 | elements, err := c.slackChannelsService.List(ctx) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | if len(elements) == 0 { 29 | cmd.Println("No Slack Channels found.") 30 | return nil 31 | } 32 | 33 | printSlackChannelsTable(cmd, elements) 34 | 35 | return nil 36 | }, 37 | PreRunE: func(cmd *cobra.Command, args []string) error { 38 | if err := addClientConfigOptions(cmd, c.config); err != nil { 39 | return err 40 | } 41 | return c.setSlackChannelsService(cmd, args) 42 | }, 43 | } 44 | 45 | c.root.AddCommand(cmd) 46 | return addClientFlags(cmd) 47 | } 48 | 49 | func (c *command) setSlackChannelsService(cmd *cobra.Command, args []string) (err error) { 50 | if c.slackChannelsService != nil { 51 | return nil 52 | } 53 | client, err := c.getClient(cmd) 54 | if err != nil { 55 | return err 56 | } 57 | c.slackChannelsService = client.SlackChannels 58 | return nil 59 | } 60 | 61 | type slackChannelsService interface { 62 | List(ctx context.Context) (channels []newreleases.SlackChannel, err error) 63 | } 64 | 65 | func printSlackChannelsTable(cmd *cobra.Command, elements []newreleases.SlackChannel) { 66 | table := newTable(cmd.OutOrStdout()) 67 | table.SetHeader([]string{"ID", "Workspace", "Channel"}) 68 | for _, e := range elements { 69 | table.Append([]string{e.ID, e.TeamName, e.Channel}) 70 | } 71 | table.Render() 72 | } 73 | -------------------------------------------------------------------------------- /newreleases/cmd/slack_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "context" 11 | "testing" 12 | 13 | "newreleases.io/cmd/newreleases/cmd" 14 | "newreleases.io/newreleases" 15 | ) 16 | 17 | func TestSlackCmd(t *testing.T) { 18 | for _, tc := range []struct { 19 | name string 20 | slackChannelsService cmd.SlackChannelsService 21 | wantOutput string 22 | wantError error 23 | }{ 24 | { 25 | name: "no channels", 26 | slackChannelsService: newMockSlackChannelsService(nil, nil), 27 | wantOutput: "No Slack Channels found.\n", 28 | }, 29 | { 30 | name: "with channels", 31 | slackChannelsService: newMockSlackChannelsService([]newreleases.SlackChannel{ 32 | { 33 | ID: "4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1", 34 | TeamName: "NewReleases", 35 | Channel: "@bob", 36 | }, 37 | { 38 | ID: "c6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1", 39 | TeamName: "Awesome project", 40 | Channel: "general", 41 | }, 42 | }, nil), 43 | wantOutput: "ID WORKSPACE CHANNEL \n4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1 NewReleases @bob \nc6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1 Awesome project general \n", 44 | }, 45 | { 46 | name: "error", 47 | slackChannelsService: newMockSlackChannelsService(nil, errTest), 48 | wantError: errTest, 49 | }, 50 | } { 51 | t.Run(tc.name, func(t *testing.T) { 52 | var outputBuf bytes.Buffer 53 | if err := newCommand(t, 54 | cmd.WithArgs("slack"), 55 | cmd.WithOutput(&outputBuf), 56 | cmd.WithSlackChannelsService(tc.slackChannelsService), 57 | ).Execute(); err != tc.wantError { 58 | t.Fatalf("got error %v, want %v", err, tc.wantError) 59 | } 60 | 61 | gotOutput := outputBuf.String() 62 | if gotOutput != tc.wantOutput { 63 | t.Errorf("got output %q, want %q", gotOutput, tc.wantOutput) 64 | } 65 | }) 66 | } 67 | } 68 | 69 | type mockSlackChannelsService struct { 70 | channels []newreleases.SlackChannel 71 | err error 72 | } 73 | 74 | func newMockSlackChannelsService(channels []newreleases.SlackChannel, err error) (s mockSlackChannelsService) { 75 | return mockSlackChannelsService{channels: channels, err: err} 76 | } 77 | 78 | func (s mockSlackChannelsService) List(ctx context.Context) (channels []newreleases.SlackChannel, err error) { 79 | return s.channels, s.err 80 | } 81 | -------------------------------------------------------------------------------- /newreleases/cmd/tag.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initTagCmd() (err error) { 16 | cmd := &cobra.Command{ 17 | Use: "tag", 18 | Short: "Manage tags", 19 | } 20 | 21 | if err := c.initTagListCmd(cmd); err != nil { 22 | return err 23 | } 24 | if err := c.initTagGetCmd(cmd); err != nil { 25 | return err 26 | } 27 | if err := c.initTagAddCmd(cmd); err != nil { 28 | return err 29 | } 30 | if err := c.initTagUpdateCmd(cmd); err != nil { 31 | return err 32 | } 33 | if err := c.initTagRemoveCmd(cmd); err != nil { 34 | return err 35 | } 36 | 37 | c.root.AddCommand(cmd) 38 | return nil 39 | } 40 | 41 | func (c *command) setTagsService(cmd *cobra.Command, args []string) error { 42 | if c.tagsService != nil { 43 | return nil 44 | } 45 | client, err := c.getClient(cmd) 46 | if err != nil { 47 | return err 48 | } 49 | c.tagsService = client.Tags 50 | return nil 51 | } 52 | 53 | type tagsService interface { 54 | List(ctx context.Context) ([]newreleases.Tag, error) 55 | Get(ctx context.Context, id string) (*newreleases.Tag, error) 56 | Add(ctx context.Context, name string) (*newreleases.Tag, error) 57 | Update(ctx context.Context, id, name string) (*newreleases.Tag, error) 58 | Delete(ctx context.Context, id string) error 59 | } 60 | 61 | func printTagsTable(cmd *cobra.Command, tags []newreleases.Tag) { 62 | table := newTable(cmd.OutOrStdout()) 63 | table.SetHeader([]string{"ID", "Name"}) 64 | for _, tag := range tags { 65 | table.Append([]string{tag.ID, tag.Name}) 66 | } 67 | table.Render() 68 | } 69 | 70 | func printTag(cmd *cobra.Command, t *newreleases.Tag) { 71 | table := newTable(cmd.OutOrStdout()) 72 | table.Append([]string{"ID:", t.ID}) 73 | table.Append([]string{"Name:", t.Name}) 74 | table.Render() 75 | } 76 | -------------------------------------------------------------------------------- /newreleases/cmd/tag_add.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func (c *command) initTagAddCmd(tagCmd *cobra.Command) (err error) { 13 | cmd := &cobra.Command{ 14 | Use: "add TAG_NAME", 15 | Short: "Add a new tag with a provided name", 16 | RunE: func(cmd *cobra.Command, args []string) (err error) { 17 | ctx, cancel := newClientContext(c.config) 18 | defer cancel() 19 | 20 | if len(args) != 1 { 21 | return cmd.Help() 22 | } 23 | 24 | tag, err := c.tagsService.Add(ctx, args[0]) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | printTag(cmd, tag) 30 | return nil 31 | }, 32 | PreRunE: func(cmd *cobra.Command, args []string) error { 33 | if err := addClientConfigOptions(cmd, c.config); err != nil { 34 | return err 35 | } 36 | return c.setTagsService(cmd, args) 37 | }, 38 | } 39 | 40 | tagCmd.AddCommand(cmd) 41 | return addClientFlags(cmd) 42 | } 43 | -------------------------------------------------------------------------------- /newreleases/cmd/tag_add_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "testing" 11 | 12 | "newreleases.io/cmd/newreleases/cmd" 13 | ) 14 | 15 | func TestTagCmd_Add(t *testing.T) { 16 | for _, tc := range []struct { 17 | name string 18 | tagsService cmd.TagsService 19 | wantOutput string 20 | wantError error 21 | }{ 22 | { 23 | name: "new", 24 | tagsService: newMockTagsService(nil, nil), 25 | wantOutput: "ID: new\nName: Cool\n", 26 | }, 27 | { 28 | name: "error", 29 | tagsService: newMockTagsService(nil, errTest), 30 | wantError: errTest, 31 | }, 32 | } { 33 | t.Run(tc.name, func(t *testing.T) { 34 | var outputBuf bytes.Buffer 35 | if err := newCommand(t, 36 | cmd.WithArgs("tag", "add", "Cool"), 37 | cmd.WithOutput(&outputBuf), 38 | cmd.WithTagsService(tc.tagsService), 39 | ).Execute(); err != tc.wantError { 40 | t.Fatalf("got error %v, want %v", err, tc.wantError) 41 | } 42 | 43 | wantOutput := trimSpace(tc.wantOutput) 44 | gotOutput := trimSpace(outputBuf.String()) 45 | if gotOutput != wantOutput { 46 | t.Errorf("got output %q, want %q", gotOutput, wantOutput) 47 | } 48 | }) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /newreleases/cmd/tag_get.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "errors" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initTagGetCmd(tagCmd *cobra.Command) (err error) { 16 | cmd := &cobra.Command{ 17 | Use: "get TAG_ID", 18 | Short: "Get information about a tag by its ID", 19 | RunE: func(cmd *cobra.Command, args []string) (err error) { 20 | ctx, cancel := newClientContext(c.config) 21 | defer cancel() 22 | 23 | if len(args) != 1 { 24 | return cmd.Help() 25 | } 26 | 27 | tag, err := c.tagsService.Get(ctx, args[0]) 28 | if err != nil { 29 | if errors.Is(err, newreleases.ErrNotFound) { 30 | cmd.Println("Tag not found.") 31 | return nil 32 | } 33 | return err 34 | } 35 | 36 | if tag == nil { 37 | cmd.Println("Tag not found.") 38 | return nil 39 | } 40 | 41 | printTag(cmd, tag) 42 | return nil 43 | }, 44 | PreRunE: func(cmd *cobra.Command, args []string) error { 45 | if err := addClientConfigOptions(cmd, c.config); err != nil { 46 | return err 47 | } 48 | return c.setTagsService(cmd, args) 49 | }, 50 | } 51 | 52 | tagCmd.AddCommand(cmd) 53 | return addClientFlags(cmd) 54 | } 55 | -------------------------------------------------------------------------------- /newreleases/cmd/tag_get_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "testing" 11 | 12 | "newreleases.io/cmd/newreleases/cmd" 13 | ) 14 | 15 | func TestTagCmd_Get(t *testing.T) { 16 | for _, tc := range []struct { 17 | name string 18 | tagsService cmd.TagsService 19 | wantOutput string 20 | wantError error 21 | }{ 22 | { 23 | name: "no tags", 24 | tagsService: newMockTagsService(nil, nil), 25 | wantOutput: "Tag not found.\n", 26 | }, 27 | { 28 | name: "with tags", 29 | tagsService: newMockTagsService(fullTags, nil), 30 | wantOutput: "ID: 33f1db7254b9\nName: Cool\n", 31 | }, 32 | { 33 | name: "error", 34 | tagsService: newMockTagsService(fullTags, errTest), 35 | wantError: errTest, 36 | }, 37 | } { 38 | t.Run(tc.name, func(t *testing.T) { 39 | var outputBuf bytes.Buffer 40 | if err := newCommand(t, 41 | cmd.WithArgs("tag", "get", "33f1db7254b9"), 42 | cmd.WithOutput(&outputBuf), 43 | cmd.WithTagsService(tc.tagsService), 44 | ).Execute(); err != tc.wantError { 45 | t.Fatalf("got error %v, want %v", err, tc.wantError) 46 | } 47 | 48 | wantOutput := trimSpace(tc.wantOutput) 49 | gotOutput := trimSpace(outputBuf.String()) 50 | if gotOutput != wantOutput { 51 | t.Errorf("got output %q, want %q", gotOutput, wantOutput) 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /newreleases/cmd/tag_list.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func (c *command) initTagListCmd(tagCmd *cobra.Command) (err error) { 13 | cmd := &cobra.Command{ 14 | Use: "list", 15 | Short: "Get all tags", 16 | RunE: func(cmd *cobra.Command, args []string) (err error) { 17 | ctx, cancel := newClientContext(c.config) 18 | defer cancel() 19 | 20 | tags, err := c.tagsService.List(ctx) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | if len(tags) == 0 { 26 | cmd.Println("No tags found.") 27 | return nil 28 | } 29 | 30 | printTagsTable(cmd, tags) 31 | 32 | return nil 33 | }, 34 | PreRunE: func(cmd *cobra.Command, args []string) error { 35 | if err := addClientConfigOptions(cmd, c.config); err != nil { 36 | return err 37 | } 38 | return c.setTagsService(cmd, args) 39 | }, 40 | } 41 | 42 | tagCmd.AddCommand(cmd) 43 | return addClientFlags(cmd) 44 | } 45 | -------------------------------------------------------------------------------- /newreleases/cmd/tag_list_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "testing" 11 | 12 | "newreleases.io/cmd/newreleases/cmd" 13 | ) 14 | 15 | func TestTagCmd_List(t *testing.T) { 16 | for _, tc := range []struct { 17 | name string 18 | tagsService cmd.TagsService 19 | wantOutput string 20 | wantError error 21 | }{ 22 | { 23 | name: "no tags", 24 | tagsService: newMockTagsService(nil, nil), 25 | wantOutput: "No tags found.\n", 26 | }, 27 | { 28 | name: "with tags", 29 | tagsService: newMockTagsService(fullTags, nil), 30 | wantOutput: "ID NAME \n33f1db7254b9 Cool \n1d33b7254b9f Awesome \n", 31 | }, 32 | { 33 | name: "error", 34 | tagsService: newMockTagsService(nil, errTest), 35 | wantError: errTest, 36 | }, 37 | } { 38 | t.Run(tc.name, func(t *testing.T) { 39 | var outputBuf bytes.Buffer 40 | if err := newCommand(t, 41 | cmd.WithArgs("tag", "list"), 42 | cmd.WithOutput(&outputBuf), 43 | cmd.WithTagsService(tc.tagsService), 44 | ).Execute(); err != tc.wantError { 45 | t.Fatalf("got error %v, want %v", err, tc.wantError) 46 | } 47 | 48 | wantOutput := trimSpace(tc.wantOutput) 49 | gotOutput := trimSpace(outputBuf.String()) 50 | if gotOutput != wantOutput { 51 | t.Errorf("got output %q, want %q", gotOutput, wantOutput) 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /newreleases/cmd/tag_remove.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "errors" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initTagRemoveCmd(tagCmd *cobra.Command) (err error) { 16 | cmd := &cobra.Command{ 17 | Use: "remove TAG_ID", 18 | Short: "Remove a tag by its ID", 19 | RunE: func(cmd *cobra.Command, args []string) (err error) { 20 | ctx, cancel := newClientContext(c.config) 21 | defer cancel() 22 | 23 | if len(args) != 1 { 24 | return cmd.Help() 25 | } 26 | 27 | if err := c.tagsService.Delete(ctx, args[0]); err != nil { 28 | if errors.Is(err, newreleases.ErrNotFound) { 29 | cmd.Println("Tag not found.") 30 | return nil 31 | } 32 | return err 33 | } 34 | return nil 35 | }, 36 | PreRunE: func(cmd *cobra.Command, args []string) error { 37 | if err := addClientConfigOptions(cmd, c.config); err != nil { 38 | return err 39 | } 40 | return c.setTagsService(cmd, args) 41 | }, 42 | } 43 | 44 | tagCmd.AddCommand(cmd) 45 | return addClientFlags(cmd) 46 | } 47 | -------------------------------------------------------------------------------- /newreleases/cmd/tag_remove_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "testing" 11 | 12 | "newreleases.io/cmd/newreleases/cmd" 13 | ) 14 | 15 | func TestTagCmd_Remove(t *testing.T) { 16 | for _, tc := range []struct { 17 | name string 18 | tagsService cmd.TagsService 19 | wantOutput string 20 | wantError error 21 | }{ 22 | { 23 | name: "no tags", 24 | tagsService: newMockTagsService(nil, nil), 25 | wantOutput: "Tag not found.\n", 26 | }, 27 | { 28 | name: "with tags", 29 | tagsService: newMockTagsService(fullTags, nil), 30 | wantOutput: "", 31 | }, 32 | { 33 | name: "error", 34 | tagsService: newMockTagsService(fullTags, errTest), 35 | wantError: errTest, 36 | }, 37 | } { 38 | t.Run(tc.name, func(t *testing.T) { 39 | var outputBuf bytes.Buffer 40 | if err := newCommand(t, 41 | cmd.WithArgs("tag", "remove", "33f1db7254b9"), 42 | cmd.WithOutput(&outputBuf), 43 | cmd.WithTagsService(tc.tagsService), 44 | ).Execute(); err != tc.wantError { 45 | t.Fatalf("got error %v, want %v", err, tc.wantError) 46 | } 47 | 48 | wantOutput := trimSpace(tc.wantOutput) 49 | gotOutput := trimSpace(outputBuf.String()) 50 | if gotOutput != wantOutput { 51 | t.Errorf("got output %q, want %q", gotOutput, wantOutput) 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /newreleases/cmd/tag_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "context" 10 | 11 | "newreleases.io/newreleases" 12 | ) 13 | 14 | type mockTagsService struct { 15 | tags []newreleases.Tag 16 | err error 17 | } 18 | 19 | func newMockTagsService(tags []newreleases.Tag, err error) mockTagsService { 20 | return mockTagsService{tags: tags, err: err} 21 | } 22 | 23 | func (s mockTagsService) List(ctx context.Context) ([]newreleases.Tag, error) { 24 | return s.tags, s.err 25 | } 26 | 27 | func (s mockTagsService) Get(ctx context.Context, id string) (*newreleases.Tag, error) { 28 | if len(s.tags) == 0 { 29 | return nil, newreleases.ErrNotFound 30 | } 31 | return &s.tags[0], s.err 32 | } 33 | 34 | func (s mockTagsService) Add(ctx context.Context, name string) (*newreleases.Tag, error) { 35 | return &newreleases.Tag{ 36 | ID: "new", 37 | Name: name, 38 | }, s.err 39 | } 40 | 41 | func (s mockTagsService) Update(ctx context.Context, id, name string) (*newreleases.Tag, error) { 42 | if len(s.tags) == 0 { 43 | return nil, newreleases.ErrNotFound 44 | } 45 | tag := &s.tags[0] 46 | tag.Name = name 47 | return tag, s.err 48 | } 49 | 50 | func (s mockTagsService) Delete(ctx context.Context, id string) error { 51 | if len(s.tags) == 0 { 52 | return newreleases.ErrNotFound 53 | } 54 | return s.err 55 | } 56 | 57 | var fullTags = []newreleases.Tag{ 58 | { 59 | ID: "33f1db7254b9", 60 | Name: "Cool", 61 | }, 62 | { 63 | ID: "1d33b7254b9f", 64 | Name: "Awesome", 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /newreleases/cmd/tag_update.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "errors" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initTagUpdateCmd(tagCmd *cobra.Command) (err error) { 16 | var ( 17 | optionNameName = "name" 18 | ) 19 | 20 | cmd := &cobra.Command{ 21 | Use: "update TAG_ID", 22 | Short: "Update a tag referenced by its ID", 23 | RunE: func(cmd *cobra.Command, args []string) (err error) { 24 | ctx, cancel := newClientContext(c.config) 25 | defer cancel() 26 | 27 | if len(args) != 1 { 28 | return cmd.Help() 29 | } 30 | 31 | flags := cmd.Flags() 32 | name, err := flags.GetString(optionNameName) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | if name == "" { 38 | cmd.Println("Option --name is required.") 39 | return nil 40 | } 41 | 42 | tag, err := c.tagsService.Update(ctx, args[0], name) 43 | if err != nil { 44 | if errors.Is(err, newreleases.ErrNotFound) { 45 | cmd.Println("Tag not found.") 46 | return nil 47 | } 48 | return err 49 | } 50 | 51 | printTag(cmd, tag) 52 | return nil 53 | }, 54 | PreRunE: func(cmd *cobra.Command, args []string) error { 55 | if err := addClientConfigOptions(cmd, c.config); err != nil { 56 | return err 57 | } 58 | return c.setTagsService(cmd, args) 59 | }, 60 | } 61 | 62 | cmd.Flags().String(optionNameName, "", "tag name") 63 | 64 | tagCmd.AddCommand(cmd) 65 | return addClientFlags(cmd) 66 | } 67 | -------------------------------------------------------------------------------- /newreleases/cmd/tag_update_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "testing" 11 | 12 | "newreleases.io/cmd/newreleases/cmd" 13 | ) 14 | 15 | func TestTagCmd_Update(t *testing.T) { 16 | for _, tc := range []struct { 17 | name string 18 | tagsService cmd.TagsService 19 | args []string 20 | wantOutput string 21 | wantError error 22 | }{ 23 | { 24 | name: "name", 25 | tagsService: newMockTagsService(fullTags, nil), 26 | args: []string{"--name", "Interesting"}, 27 | wantOutput: "ID: 33f1db7254b9\nName: Interesting\n", 28 | }, 29 | { 30 | name: "no name flag", 31 | tagsService: newMockTagsService(fullTags, nil), 32 | wantOutput: "Option --name is required.\n", 33 | }, 34 | { 35 | name: "empty name", 36 | tagsService: newMockTagsService(fullTags, nil), 37 | args: []string{"--name", ""}, 38 | wantOutput: "Option --name is required.\n", 39 | }, 40 | { 41 | name: "error", 42 | tagsService: newMockTagsService(fullTags, errTest), 43 | args: []string{"--name", "Interesting"}, 44 | wantError: errTest, 45 | }, 46 | } { 47 | t.Run(tc.name, func(t *testing.T) { 48 | var outputBuf bytes.Buffer 49 | if err := newCommand(t, 50 | cmd.WithArgs(append([]string{"tag", "update", "33f1db7254b9"}, tc.args...)...), 51 | cmd.WithOutput(&outputBuf), 52 | cmd.WithTagsService(tc.tagsService), 53 | ).Execute(); err != tc.wantError { 54 | t.Fatalf("got error %v, want %v", err, tc.wantError) 55 | } 56 | 57 | wantOutput := trimSpace(tc.wantOutput) 58 | gotOutput := trimSpace(outputBuf.String()) 59 | if gotOutput != wantOutput { 60 | t.Errorf("got output %q, want %q", gotOutput, wantOutput) 61 | } 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /newreleases/cmd/telegram.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initTelegramCmd() (err error) { 16 | cmd := &cobra.Command{ 17 | Use: "telegram", 18 | Short: "List Telegram integrations", 19 | RunE: func(cmd *cobra.Command, args []string) (err error) { 20 | ctx, cancel := newClientContext(c.config) 21 | defer cancel() 22 | 23 | chats, err := c.telegramChatsService.List(ctx) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | if len(chats) == 0 { 29 | cmd.Println("No Telegram Chats found.") 30 | return nil 31 | } 32 | 33 | printTelegramChatsTable(cmd, chats) 34 | 35 | return nil 36 | }, 37 | PreRunE: func(cmd *cobra.Command, args []string) error { 38 | if err := addClientConfigOptions(cmd, c.config); err != nil { 39 | return err 40 | } 41 | return c.setTelegramChatsService(cmd, args) 42 | }, 43 | } 44 | 45 | c.root.AddCommand(cmd) 46 | return addClientFlags(cmd) 47 | } 48 | 49 | func (c *command) setTelegramChatsService(cmd *cobra.Command, args []string) (err error) { 50 | if c.telegramChatsService != nil { 51 | return nil 52 | } 53 | client, err := c.getClient(cmd) 54 | if err != nil { 55 | return err 56 | } 57 | c.telegramChatsService = client.TelegramChats 58 | return nil 59 | } 60 | 61 | type telegramChatsService interface { 62 | List(ctx context.Context) (chats []newreleases.TelegramChat, err error) 63 | } 64 | 65 | func printTelegramChatsTable(cmd *cobra.Command, chats []newreleases.TelegramChat) { 66 | table := newTable(cmd.OutOrStdout()) 67 | table.SetHeader([]string{"ID", "Chat", "Type"}) 68 | for _, e := range chats { 69 | table.Append([]string{e.ID, e.Name, e.Type}) 70 | } 71 | table.Render() 72 | } 73 | -------------------------------------------------------------------------------- /newreleases/cmd/telegram_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "context" 11 | "testing" 12 | 13 | "newreleases.io/cmd/newreleases/cmd" 14 | "newreleases.io/newreleases" 15 | ) 16 | 17 | func TestTelegramCmd(t *testing.T) { 18 | for _, tc := range []struct { 19 | name string 20 | telegramChatsService cmd.TelegramChatsService 21 | wantOutput string 22 | wantError error 23 | }{ 24 | { 25 | name: "no chats", 26 | telegramChatsService: newMockTelegramChatssService(nil, nil), 27 | wantOutput: "No Telegram Chats found.\n", 28 | }, 29 | { 30 | name: "with chats", 31 | telegramChatsService: newMockTelegramChatssService([]newreleases.TelegramChat{ 32 | { 33 | ID: "4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1", 34 | Name: "NewReleases", 35 | }, 36 | { 37 | ID: "c6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1", 38 | Name: "Awesome project", 39 | }, 40 | }, nil), 41 | wantOutput: "ID CHAT TYPE \n4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1 NewReleases \nc6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1 Awesome project \n", 42 | }, 43 | { 44 | name: "error", 45 | telegramChatsService: newMockTelegramChatssService(nil, errTest), 46 | wantError: errTest, 47 | }, 48 | } { 49 | t.Run(tc.name, func(t *testing.T) { 50 | var outputBuf bytes.Buffer 51 | if err := newCommand(t, 52 | cmd.WithArgs("telegram"), 53 | cmd.WithOutput(&outputBuf), 54 | cmd.WithTelegramChatsService(tc.telegramChatsService), 55 | ).Execute(); err != tc.wantError { 56 | t.Fatalf("got error %v, want %v", err, tc.wantError) 57 | } 58 | 59 | gotOutput := outputBuf.String() 60 | if gotOutput != tc.wantOutput { 61 | t.Errorf("got output %q, want %q", gotOutput, tc.wantOutput) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | type mockTelegramChatsService struct { 68 | chats []newreleases.TelegramChat 69 | err error 70 | } 71 | 72 | func newMockTelegramChatssService(chats []newreleases.TelegramChat, err error) (s mockTelegramChatsService) { 73 | return mockTelegramChatsService{chats: chats, err: err} 74 | } 75 | 76 | func (s mockTelegramChatsService) List(ctx context.Context) (chats []newreleases.TelegramChat, err error) { 77 | return s.chats, s.err 78 | } 79 | -------------------------------------------------------------------------------- /newreleases/cmd/terminal.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "os" 10 | "strings" 11 | 12 | "github.com/spf13/cobra" 13 | "golang.org/x/term" 14 | ) 15 | 16 | type passwordReader interface { 17 | ReadPassword() (password string, err error) 18 | } 19 | 20 | type stdInPasswordReader struct{} 21 | 22 | func (stdInPasswordReader) ReadPassword() (password string, err error) { 23 | v, err := term.ReadPassword(int(os.Stdin.Fd())) 24 | if err != nil { 25 | return "", err 26 | } 27 | return string(v), err 28 | } 29 | 30 | func terminalPromptPassword(cmd *cobra.Command, r passwordReader, title string) (password string, err error) { 31 | cmd.Print(title + ": ") 32 | password, err = r.ReadPassword() 33 | cmd.Println() 34 | if err != nil { 35 | return "", err 36 | } 37 | return password, nil 38 | } 39 | 40 | func terminalPrompt(cmd *cobra.Command, reader interface{ ReadString(byte) (string, error) }, title string) (value string, err error) { 41 | cmd.Print(title + ": ") 42 | value, err = reader.ReadString('\n') 43 | if err != nil { 44 | return "", err 45 | } 46 | return strings.TrimSpace(value), nil 47 | } 48 | -------------------------------------------------------------------------------- /newreleases/cmd/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "github.com/spf13/cobra" 10 | 11 | nrcmd "newreleases.io/cmd" 12 | ) 13 | 14 | func (c *command) initVersionCmd() { 15 | c.root.AddCommand(&cobra.Command{ 16 | Use: "version", 17 | Short: "Print version number", 18 | Run: func(cmd *cobra.Command, args []string) { 19 | cmd.Println(nrcmd.Version()) 20 | }, 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /newreleases/cmd/version_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "testing" 11 | 12 | nrcmd "newreleases.io/cmd" 13 | "newreleases.io/cmd/newreleases/cmd" 14 | ) 15 | 16 | func TestVersionCmd(t *testing.T) { 17 | var outputBuf bytes.Buffer 18 | if err := newCommand(t, 19 | cmd.WithArgs("version"), 20 | cmd.WithOutput(&outputBuf), 21 | ).Execute(); err != nil { 22 | t.Fatal(err) 23 | } 24 | 25 | want := nrcmd.Version() + "\n" 26 | got := outputBuf.String() 27 | if got != want { 28 | t.Errorf("got output %q, want %q", got, want) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /newreleases/cmd/webhook.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/spf13/cobra" 12 | "newreleases.io/newreleases" 13 | ) 14 | 15 | func (c *command) initWebhookCmd() (err error) { 16 | cmd := &cobra.Command{ 17 | Use: "webhook", 18 | Short: "List custom Webhooks", 19 | RunE: func(cmd *cobra.Command, args []string) (err error) { 20 | ctx, cancel := newClientContext(c.config) 21 | defer cancel() 22 | 23 | webhooks, err := c.webhooksService.List(ctx) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | if len(webhooks) == 0 { 29 | cmd.Println("No Webhooks found.") 30 | return nil 31 | } 32 | 33 | printWebhooksTable(cmd, webhooks) 34 | 35 | return nil 36 | }, 37 | PreRunE: func(cmd *cobra.Command, args []string) error { 38 | if err := addClientConfigOptions(cmd, c.config); err != nil { 39 | return err 40 | } 41 | return c.setWebhooksService(cmd, args) 42 | }, 43 | } 44 | 45 | c.root.AddCommand(cmd) 46 | return addClientFlags(cmd) 47 | } 48 | 49 | func (c *command) setWebhooksService(cmd *cobra.Command, args []string) (err error) { 50 | if c.webhooksService != nil { 51 | return nil 52 | } 53 | client, err := c.getClient(cmd) 54 | if err != nil { 55 | return err 56 | } 57 | c.webhooksService = client.Webhooks 58 | return nil 59 | } 60 | 61 | type webhooksService interface { 62 | List(ctx context.Context) (webhooks []newreleases.Webhook, err error) 63 | } 64 | 65 | func printWebhooksTable(cmd *cobra.Command, webhooks []newreleases.Webhook) { 66 | table := newTable(cmd.OutOrStdout()) 67 | table.SetHeader([]string{"ID", "Name"}) 68 | for _, e := range webhooks { 69 | table.Append([]string{e.ID, e.Name}) 70 | } 71 | table.Render() 72 | } 73 | -------------------------------------------------------------------------------- /newreleases/cmd/webhook_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd_test 7 | 8 | import ( 9 | "bytes" 10 | "context" 11 | "testing" 12 | 13 | "newreleases.io/cmd/newreleases/cmd" 14 | "newreleases.io/newreleases" 15 | ) 16 | 17 | func TestWebhookCmd(t *testing.T) { 18 | for _, tc := range []struct { 19 | name string 20 | webhooksService cmd.WebhooksService 21 | wantOutput string 22 | wantError error 23 | }{ 24 | { 25 | name: "no webhooks", 26 | webhooksService: newMockWebhooksService(nil, nil), 27 | wantOutput: "No Webhooks found.\n", 28 | }, 29 | { 30 | name: "with webhooks", 31 | webhooksService: newMockWebhooksService([]newreleases.Webhook{ 32 | { 33 | ID: "4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1", 34 | Name: "NewReleases", 35 | }, 36 | { 37 | ID: "c6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1", 38 | Name: "Awesome project", 39 | }, 40 | }, nil), 41 | wantOutput: "ID NAME \n4qOpc9t16rpymcw7z8jwn5y6anne0sg5a9b1 NewReleases \nc6anne0sg9t4qOp16rpymcw7z8jwn5y5a9b1 Awesome project \n", 42 | }, 43 | { 44 | name: "error", 45 | webhooksService: newMockWebhooksService(nil, errTest), 46 | wantError: errTest, 47 | }, 48 | } { 49 | t.Run(tc.name, func(t *testing.T) { 50 | var outputBuf bytes.Buffer 51 | if err := newCommand(t, 52 | cmd.WithArgs("webhook"), 53 | cmd.WithOutput(&outputBuf), 54 | cmd.WithWebhooksService(tc.webhooksService), 55 | ).Execute(); err != tc.wantError { 56 | t.Fatalf("got error %v, want %v", err, tc.wantError) 57 | } 58 | 59 | gotOutput := outputBuf.String() 60 | if gotOutput != tc.wantOutput { 61 | t.Errorf("got output %q, want %q", gotOutput, tc.wantOutput) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | type mockWebhooksService struct { 68 | webhooks []newreleases.Webhook 69 | err error 70 | } 71 | 72 | func newMockWebhooksService(webhooks []newreleases.Webhook, err error) (s mockWebhooksService) { 73 | return mockWebhooksService{webhooks: webhooks, err: err} 74 | } 75 | 76 | func (s mockWebhooksService) List(ctx context.Context) (webhooks []newreleases.Webhook, err error) { 77 | return s.webhooks, s.err 78 | } 79 | -------------------------------------------------------------------------------- /newreleases/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | 12 | "newreleases.io/cmd/newreleases/cmd" 13 | ) 14 | 15 | func main() { 16 | if err := cmd.Execute(); err != nil { 17 | fmt.Fprintln(os.Stderr, "Error:", err) 18 | os.Exit(1) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, NewReleases CLI AUTHORS. 2 | // All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import "runtime/debug" 9 | 10 | // automatically set on release 11 | // and updated with vcs revision on init 12 | var version = "0.0.0" 13 | 14 | func init() { 15 | info, ok := debug.ReadBuildInfo() 16 | if !ok { 17 | return 18 | } 19 | 20 | var revision string 21 | var dirtyBuild bool 22 | for _, s := range info.Settings { 23 | switch s.Key { 24 | case "vcs.revision": 25 | revision = s.Value 26 | case "vcs.modified": 27 | dirtyBuild = s.Value == "true" 28 | } 29 | } 30 | 31 | if len(revision) == 0 { 32 | return 33 | } 34 | 35 | if len(revision) > 7 { 36 | revision = revision[:7] 37 | } 38 | 39 | version += "-" + revision 40 | if dirtyBuild { 41 | version += "-dirty" 42 | } 43 | } 44 | 45 | func Version() string { 46 | return version 47 | } 48 | --------------------------------------------------------------------------------