├── .gitignore ├── recording.gif ├── LICENSE ├── spinner └── spinner.go ├── README.md ├── cmd └── checkr │ ├── helpers.go │ └── main.go └── .goreleaser.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | dist -------------------------------------------------------------------------------- /recording.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shopnilsazal/checkr/HEAD/recording.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Rafiqul Hasan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /spinner/spinner.go: -------------------------------------------------------------------------------- 1 | package spinner 2 | 3 | import ( 4 | "fmt" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | // ClearLine go to the beggining of the line and clear it 10 | const ClearLine = "\r\033[K" 11 | 12 | // Spin type 13 | var Spin = `⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏` 14 | 15 | // Spinner main type 16 | type Spinner struct { 17 | frames []rune 18 | pos int 19 | active uint64 20 | text string 21 | } 22 | 23 | // New Spinner with args 24 | func New(text string) *Spinner { 25 | s := &Spinner{ 26 | text: ClearLine + text, 27 | } 28 | s.Set(Spin) 29 | return s 30 | } 31 | 32 | // Set frames to the given string which must not use spaces. 33 | func (s *Spinner) Set(frames string) { 34 | s.frames = []rune(frames) 35 | } 36 | 37 | // Start shows the spinner. 38 | func (s *Spinner) Start() *Spinner { 39 | if atomic.LoadUint64(&s.active) > 0 { 40 | return s 41 | } 42 | atomic.StoreUint64(&s.active, 1) 43 | go func() { 44 | for atomic.LoadUint64(&s.active) > 0 { 45 | fmt.Printf(s.text, s.next()) 46 | time.Sleep(100 * time.Millisecond) 47 | } 48 | }() 49 | return s 50 | } 51 | 52 | // Stop hides the spinner. 53 | func (s *Spinner) Stop() bool { 54 | if x := atomic.SwapUint64(&s.active, 0); x > 0 { 55 | fmt.Printf(ClearLine) 56 | return true 57 | } 58 | return false 59 | } 60 | 61 | func (s *Spinner) next() string { 62 | r := s.frames[s.pos%len(s.frames)] 63 | s.pos++ 64 | return string(r) 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # checkr 2 | 3 | ### A cli tool to check whether a package name is available on different package managers. 4 | 5 | ![Screen Recording](https://raw.githubusercontent.com/shopnilsazal/checkr/master/recording.gif) 6 | 7 | 8 | ## Install 9 | 10 | ``` 11 | $ brew install shopnilsazal/tap/checkr 12 | ``` 13 | 14 | Or you can download binary from [releases](https://github.com/shopnilsazal/checkr/releases) page. 15 | 16 | 17 | ## Usage 18 | 19 | ``` 20 | $ checkr -n packageName -m packageManager 21 | $ checkr -n django -m pip 22 | 23 | To search from multiple package managers, write them using comma 24 | $ checkr -n django -m pip,gem,npm 25 | 26 | To search from all supported package managers, write 'all' keyword 27 | $ checkr -n django -m all 28 | 29 | 30 | $ checkr --help 31 | -n string 32 | Provide a package name you want to search. (default "react") 33 | -m string 34 | Provide package managers where you want to search. (default "npm") 35 | 36 | Examples 37 | $ checkr -n django -m pip 38 | ✖ django is unavailable on 'pip' 39 | 40 | $ checkr -n rails -m pip,gem,npm 41 | ✖ rails is unavailable on 'pip' 42 | ✖ rails is unavailable on 'gem' 43 | ✖ rails is unavailable on 'npm' 44 | 45 | $ checkr -n mynewpackage007 -m all 46 | ✔ mynewpackage007 is available on 'gem' 47 | ✔ mynewpackage007 is available on 'hex' 48 | ✔ mynewpackage007 is available on 'pip' 49 | ✔ mynewpackage007 is available on 'npm' 50 | ✔ mynewpackage007 is available on 'crates' 51 | 52 | ``` 53 | 54 | ### Available Package Manager to Search 55 | ``` 56 | pip - Python Package Index 57 | npm - NodeJS Package Manager 58 | gem - Ruby Gem 59 | hex - Package manager for the Erlang ecosystem 60 | crates - The Rust crate Registry 61 | ``` -------------------------------------------------------------------------------- /cmd/checkr/helpers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "runtime" 7 | ) 8 | 9 | // PackageResponse Type 10 | type PackageResponse struct { 11 | packageManager string 12 | statusCode int 13 | } 14 | 15 | // Get status code from package manager 16 | func getPackageStatus(name, url string, ch chan<- PackageResponse) { 17 | resp, err := http.Get(url) 18 | if err != nil { 19 | ch <- PackageResponse{name, 500} 20 | return 21 | } 22 | ch <- PackageResponse{name, resp.StatusCode} 23 | resp.Body.Close() 24 | } 25 | 26 | // Get full url of the package manager based on name 27 | func getFullURL(url, manager, name string) string { 28 | fullURL := url + name 29 | switch manager { 30 | case "pip": 31 | fullURL = fullURL + "/json" 32 | case "gem": 33 | fullURL = fullURL + ".json" 34 | } 35 | return fullURL 36 | } 37 | 38 | // Get success and error symbols 39 | func getSymbols() (successSym, errorSym string) { 40 | if runtime.GOOS == "windows" { 41 | successSym = "\u001b[32m" + "√" + "\u001b[39m" 42 | errorSym = "\u001b[31m" + "×" + "\u001b[39m" 43 | } else { 44 | successSym = "\u001b[32m" + "✔" + "\u001b[39m" 45 | errorSym = "\u001b[31m" + "✖" + "\u001b[39m" 46 | } 47 | return 48 | } 49 | 50 | // Get the message to print on console 51 | func getMessage(resp PackageResponse, name string) string { 52 | var msg string 53 | ss, es := getSymbols() 54 | switch resp.statusCode { 55 | case 404: 56 | msg = fmt.Sprintf("%s %s is available on '%s' \n", ss, name, resp.packageManager) 57 | case 200: 58 | msg = fmt.Sprintf("%s %s is unavailable on '%s' \n", es, name, resp.packageManager) 59 | default: 60 | msg = fmt.Sprintf("%s oops! something bad happened in '%s' \n", es, resp.packageManager) 61 | } 62 | return msg 63 | } 64 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example goreleaser.yaml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | project_name: checkr 4 | builds: 5 | # You can have multiple builds defined as a yaml list 6 | - 7 | # Path to main.go file or main package. 8 | # Default is `.`. 9 | main: ./cmd/checkr 10 | # Name template for the binary final name. 11 | # Default is the name of the project directory. 12 | binary: checkr 13 | # Custom environment variables to be set during the builds. 14 | # Default is empty. 15 | env: 16 | - CGO_ENABLED=0 17 | 18 | archive: 19 | replacements: 20 | darwin: Darwin 21 | linux: Linux 22 | windows: Windows 23 | 386: i386 24 | amd64: x86_64 25 | checksum: 26 | name_template: 'checksums.txt' 27 | snapshot: 28 | name_template: "{{ .Tag }}-next" 29 | changelog: 30 | sort: asc 31 | filters: 32 | exclude: 33 | - '^docs:' 34 | - '^test:' 35 | brew: 36 | # Name template of the recipe 37 | # Default to project name 38 | name: checkr 39 | 40 | # Repository to push the tap to. 41 | github: 42 | owner: shopnilsazal 43 | name: homebrew-tap 44 | 45 | # Git author used to commit to the repository. 46 | # Defaults are shown. 47 | commit_author: 48 | name: Rafiqul Hasan 49 | email: shopnilsazal@gmail.com 50 | 51 | # Folder inside the repository to put the formula. 52 | # Default is the root folder. 53 | folder: Formula 54 | 55 | # Your app's description. 56 | # Default is empty. 57 | description: "Check whether a package name is available on different package managers." 58 | 59 | release: 60 | # You can change the name of the GitHub release. 61 | # Default is `` 62 | name_template: "{{.ProjectName}}-v{{.Version}} {{.Env.USER}}" 63 | -------------------------------------------------------------------------------- /cmd/checkr/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/shopnilsazal/checkr/spinner" 10 | ) 11 | 12 | var urls = map[string]string{ 13 | "npm": "https://registry.npmjs.org/", 14 | "pip": "https://pypi.org/pypi/", 15 | "crates": "https://crates.io/api/v1/crates/", 16 | "gem": "https://rubygems.org/api/v1/gems/", 17 | "hex": "https://hex.pm/api/packages/", 18 | } 19 | 20 | func main() { 21 | 22 | name := flag.String("n", "react", "Provide a package name you want to search.") 23 | managers := flag.String("m", "npm", "Provide package managers where you want to search.") 24 | flag.Parse() 25 | 26 | spin := spinner.New("%s Loading...") 27 | 28 | ch := make(chan PackageResponse) 29 | _, errorSym := getSymbols() 30 | 31 | allManagers := strings.Split(*managers, ",") 32 | if len(allManagers) == 1 { 33 | manager := allManagers[0] 34 | if manager == "all" { 35 | spin.Start() 36 | defer spin.Stop() 37 | 38 | for key, url := range urls { 39 | fullURL := getFullURL(url, key, *name) 40 | go getPackageStatus(key, fullURL, ch) 41 | } 42 | 43 | for range urls { 44 | res := <-ch 45 | spin.Stop() 46 | fmt.Printf(getMessage(res, *name)) 47 | spin.Start() 48 | } 49 | 50 | } else if u, ok := urls[manager]; ok { 51 | spin.Start() 52 | fullURL := getFullURL(u, manager, *name) 53 | resp, err := http.Get(fullURL) 54 | if err != nil { 55 | fmt.Printf("%s oops! something bad happened in '%s' \n", errorSym, manager) 56 | } 57 | spin.Stop() 58 | defer resp.Body.Close() 59 | fmt.Printf(getMessage(PackageResponse{manager, resp.StatusCode}, *name)) 60 | 61 | } else { 62 | fmt.Printf("%s '%s' is not a valid package manager.\n", errorSym, manager) 63 | } 64 | 65 | } else { 66 | var validManagers []string 67 | spin.Start() 68 | defer spin.Stop() 69 | 70 | for _, m := range allManagers { 71 | if u, ok := urls[m]; ok { 72 | fullURL := getFullURL(u, m, *name) 73 | go getPackageStatus(m, fullURL, ch) 74 | validManagers = append(validManagers, m) 75 | } else { 76 | fmt.Printf("%s '%s' is not a valid package manager.\n", errorSym, m) 77 | } 78 | } 79 | 80 | for range validManagers { 81 | res := <-ch 82 | spin.Stop() 83 | fmt.Printf(getMessage(res, *name)) 84 | spin.Start() 85 | } 86 | } 87 | } 88 | --------------------------------------------------------------------------------