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
toio
andos
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)
}