DEV Community

Cover image for How to create a CLI program in Go and host it on npm
Wachira
Wachira

Posted on • Originally published at blog.bywachira.com

How to create a CLI program in Go and host it on npm

Recently I started building tools that will help me automate my app deployment process easier and cheaper, to do that I knew CLIs are definitely going to be involved one way or another. So I started building two CLIs:

  • Simp CLI: It's like Makefile just easy
  • Thrusta CLI: It's a remote command execution tool

Both CLIs were built with Golang but only one is hosted on npm here, this tutorial will guide you on how you can do the same.

Note: Am still new to Go

Let's jump into it

Prerequisites

  • Basic Golang syntax and concepts (Beginner friendly)
  • NPM installed
  • Golang installed (its a process, so here is a link)

What are we building?

We are going to create a CLI that serves a directory over a HTTP server.

Lets get started

Create a new folder inside

$GOPATH/src/<github.com|gitea.com|gitlab.com|bitbucket.com>/<username>/html-server-cli
Enter fullscreen mode Exit fullscreen mode

Next, we will create our go.mod file to track packages we are going to use

go mod init
Enter fullscreen mode Exit fullscreen mode

I have my directory setup as below:

.
+-- cli
    +-- cli.go
+-- helpers
    +-- helpers.go
+-- server
    +-- server.go
+-- go.mod
+-- go.sum
+-- html-server.go
Enter fullscreen mode Exit fullscreen mode

Let add some code to our helpers/helpers.go

package helpers

import (
    "math/rand"
    "time"
)

// GeneratePortNumber handle generating port number
func GeneratePortNumber() int {
    rand.Seed(time.Now().UnixNano())

    min := 1000
    max := 99999

    port := rand.Intn(max-min+1) + min

    return port
}

Enter fullscreen mode Exit fullscreen mode

The block of code above enables us to generate a random port number, you can add more logic to see if a port is used.

Next, let's add some code to our server/server.go

package server

import (
    "log"
    "net/http"
    "strconv"

    "github.com/bywachira/html-server/helpers"
)

// Serve handles serving the directory
func Serve() {
    // Generate our port number
    port := helpers.GeneratePortNumber()
    // Inform the user which port we are running
    log.Println("We are running on port: " + strconv.Itoa(port))
    // Exit to a live server
    log.Fatal(http.ListenAndServe(":"+strconv.Itoa(port), http.FileServer(http.Dir("."))))
}
Enter fullscreen mode Exit fullscreen mode

Let's create a method to initialize our CLI

package cli

import (
    "fmt"
    "sort"

    "github.com/bywachira/html-server/server"

    "github.com/urfave/cli/v2"
)

// SetupCLI initialize cli
func SetupCLI() *cli.App {
    app := &cli.App{
        // List your commands here
        Commands: []*cli.Command{
            {
                // Provide details about our cli flags
                Name:    "run",
                Aliases: []string{"r"},
                Usage:   "Serve your HTML file",
                Action: func(c *cli.Context) error {
                    // The logic to be called when this commad is ran
                    fmt.Println("We are serving this directory")

                    server.Serve()
                    return nil
                },
            },
        },
    }

    sort.Sort(cli.FlagsByName(app.Flags))
    sort.Sort(cli.CommandsByName(app.Commands))

    // Return our cli
    return app
}

Enter fullscreen mode Exit fullscreen mode

Let's update our package main, which is html-server.go

package main

import (
    "log"
    "os"

    "github.com/bywachira/html-server/cli"
)

func main() {
    // Assign our cli to the app variable
    app := cli.SetupCLI()

    err := app.Run(os.Args)

    // Exit program when we get an error and show error
    if err != nil {
        log.Fatal("Error: ", err)
    }
}

Enter fullscreen mode Exit fullscreen mode

Note: You don't have to use github.com/urfave/cli/v2 package by default, you can use whatever package you want because all we need to publish is the binary file.

The Publishing Part

Click here to continue reading this part

Summary

  • We made a CLI in Golang
  • We generated binaries for all major OSs
  • We published our package
  • Add dist folder created by goreleaser to .gitignore file

Questions

  • Join my discord, I answer all questions, here

I have web monetization enabled so you can support my work or give me feedback on this article on what I should improve on.

Socials

Top comments (2)

Collapse
 
bias profile image
Tobias Nickel

nice, I add this article to my favorits. I already wanted a good description, after esbuild is also made using golang.

hopefully, it can help the go and JS communities to get closer together

Collapse
 
wchr profile image
Wachira

I am also hopeful