the personal playground of evan louie; developer, designer, photographer, and breaker of the web.
   (  )   /\   _                 (     
    \ |  (  \ ( \.(               )                      _____
  \  \ \  `  `   ) \             (  ___                 / _   \
 (_`    \+   . x  ( .\            \/   \____-----------/ (o)   \_
- .-               \+  ;          (  O                           \____
                          )        \_____________  `              \  /
(__                +- .( -'.- <. - _  VVVVVVV VV V\                 \/
(_____            ._._: <_ - <- _  (--  _AAAAAAA__A_/                  |
  .    /./.+-  . .- /  +--  - .     \______________//_              \_______
  (__ ' /x  / x _/ (                                  \___'          \     /
 , x / ( '  . / .  /                                      |           \   /
    /  /  _/ /    +                                      /              \/
   '  (__/                                             /                  \

Deno Installer

A Go pkg to install deno onto the host.

Update 2021-12-27:

  • Added support for Apple Silicon releases.
  • Migrated away from ioutil to io and os modules.

I've recently found myself needing to give users access to a scripting language in some apps. Deno is a secure and low footprint solution allowing people to code in both TS and JS.

This is a snippet exposing a deno pkg with an Install function. The Install function will download the latest Deno release for your host system (it supports mac/linux/windows) to your host tmp directory and return a Context value containing the DenoPath: the absolute path to the Deno binary.

package deno

import (
    "archive/zip"
    "bytes"
    "context"
    "fmt"
    "github.com/google/go-github/v41/github"
    "io"
    "net/http"
    "os"
    "path"
    "runtime"
)

type Context struct {
    DenoPath string
}

type denoNameParams struct {
    Arch    string
    Os      string
    BinName string
}

// generateDownloadURL generates a download URL for zipped deno binary.
func (denoP denoNameParams) generateDownloadURL(tag string) string {
    return fmt.Sprintf(
        "https://github.com/denoland/deno/releases/download/%s/deno-%s-%s.zip",
        tag,
        denoP.Arch,
        denoP.Os)
}

// getDenoNamingParams the parameters neeted to construct a deno download URL.
func getDenoNamingParams() (params denoNameParams, err error) {
    // Determine host OS
    switch hostOs := runtime.GOOS; hostOs {
    case "darwin":
        params.Os = "apple-darwin"
        params.BinName = "deno"
    case "linux":
        params.Os = "unknown-linux-gnu"
        params.BinName = "deno"
    case "windows":
        params.Os = "pc-windows-msvc"
        params.BinName = "deno.exe"
    default:
        return params, fmt.Errorf("unsupported OS: %s", hostOs)
    }

    // Determine host hostArch
    switch hostArch := runtime.GOARCH; hostArch {
    case "amd64":
        params.Arch = "x86_64"
    case "arm64":
        params.Arch = "aarch64"
    default:
        return params, fmt.Errorf("unsupported hostArch: %s", hostArch)
    }

    return params, nil
}

// Install Deno locally to a temporary directory
// Modifies DenoPath to point to the Deno executable
func Install() (ctx Context, err error) {
    // Fetch latest Deno release
    client := github.NewClient(nil)
    release, _, err := client.Repositories.GetLatestRelease(context.Background(), "denoland", "deno")
    if err != nil {
        return ctx, fmt.Errorf("failed to fetch latest Deno GitHub release: %s", err)
    }

    denoParams, err := getDenoNamingParams()
    if err != nil {
        return ctx, err
    }
    denoUri := denoParams.generateDownloadURL(release.GetTagName())
    fmt.Println(denoUri)

    ////////////////////////////////////////////////////////////////////////////////
    // Download latest release to temporary directory
    ////////////////////////////////////////////////////////////////////////////////
    // Scaffold temp directory
    denoDir, err := os.MkdirTemp("", "deno")
    if err != nil {
        return ctx, fmt.Errorf("failed to create Deno temporary directory %s: %s", denoDir, err)
    }

    // Download to temp dir
    resp, err := http.Get(denoUri)
    if err != nil {
        return ctx, fmt.Errorf("failed to download Deno from %s: %s", denoUri, err)
    }
    defer func(Body io.ReadCloser) {
        _ = Body.Close()
    }(resp.Body)

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return ctx, fmt.Errorf("failed to parse Deno response bytes: %s", err)
    }

    zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
    if err != nil {
        return ctx, fmt.Errorf("failed to create ZipReader: %s", err)
    }

    // Unzip each file to the temporary deno dir
    for _, zipFile := range zipReader.File {
        denoFilepath := path.Join(denoDir, zipFile.Name)
        contents, err := readZipFile(zipFile)
        if err != nil {
            return ctx, fmt.Errorf("failed to read zip file %s", zipFile.Name)
        }
        if err := os.WriteFile(denoFilepath, contents, os.ModePerm); err != nil {
            return ctx, fmt.Errorf("failed to write %s to %s", zipFile.Name, denoFilepath)
        }
    }
    denoBinPath := path.Join(denoDir, denoParams.BinName)
    ctx.DenoPath = denoBinPath

    return ctx, nil
}

func readZipFile(zf *zip.File) ([]byte, error) {
    f, err := zf.Open()
    if err != nil {
        return nil, err
    }
    defer func(f io.ReadCloser) {
        _ = f.Close()
    }(f)

    return io.ReadAll(f)
}