npmgo

package module
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 3, 2026 License: MIT Imports: 22 Imported by: 0

README

npmgo — pure Go npm package installer

Reads package-lock.json, downloads the pinned tarballs from the npm registry, verifies their integrity, and extracts them into node_modules/. No Node.js, no npm, no JavaScript runtime — pure Go with zero external dependencies.

Why

Go projects that embed a web frontend still need npm packages (react, etc.) to build that frontend. npmgo installs them directly from the lock file, removing Node.js and npm from the build chain — one static binary does the job.

Install

go install github.com/sopranoworks/npmgo/cmd/npmgo@latest

CLI usage

Run in a directory containing package-lock.json:

npmgo install                          # install everything into ./node_modules
npmgo install --production             # skip devDependencies
npmgo install --cache-dir /tmp/cache   # use a specific tarball cache

Other flags: --registry URL, --token TOKEN (private registries), --concurrency N (default 8), --lockfile PATH, --target DIR, --timeout DURATION (default 30s).

Tarballs are cached (content-addressed) under <user cache>/npmgo by default, so repeated installs are near-instant: cached packages are reused and packages already present at the locked version are skipped.

Library usage

import "github.com/sopranoworks/npmgo"

err := npmgo.Install(npmgo.Options{
    LockfilePath: "package-lock.json",
    TargetDir:    "node_modules",
})

All fields are optional; Options{} uses sensible defaults (registry https://registry.npmjs.org, cache <user cache>/npmgo, concurrency 8).

Bundling without node_modules

For Go build pipelines that bundle a frontend, npmgo can populate its tarball cache without ever extracting a node_modules directory, and an esbuild plugin resolves imports straight from those cached tarballs.

CacheOnly mode
index, err := npmgo.CachePackages(npmgo.Options{
    LockfilePath: "package-lock.json",
    CacheOnly:    true,
})
// index.Packages maps package names to their cached tarballs.
// No node_modules is created.
esbuild plugin
import (
    "github.com/evanw/esbuild/pkg/api"
    "github.com/sopranoworks/npmgo/esbuildplugin"
)

result := api.Build(api.BuildOptions{
    EntryPoints: []string{"src/main.tsx"},
    Bundle:      true,
    Plugins:     []api.Plugin{esbuildplugin.New(index)},
    // ... bundle config
})
// Bundles directly from the tarball cache — no node_modules.

The core module (github.com/sopranoworks/npmgo) has zero external dependencies. The esbuild plugin (github.com/sopranoworks/npmgo/esbuildplugin) is a separate, nested Go module that depends on esbuild; import it only if you need it, and the core installer stays dependency-free.

Features

  • Lock file parsing: lockfileVersion 1, 2 and 3
  • Integrity verification: SHA-512 (also SHA-256 / SHA-1)
  • Content-addressed tarball cache, keyed by integrity hash
  • Skips packages already installed at the locked version
  • Scoped packages (@scope/name) and nested node_modules
  • Concurrent downloads
  • Private registry support via Bearer token
  • Cache-only mode + esbuild plugin: bundle a frontend straight from the tarball cache, with no node_modules on disk

Limitations

  • Requires a package-lock.json; it does not resolve dependencies from package.json or generate a lock file.
  • Does not run lifecycle scripts (preinstall, postinstall, etc.).

License

MIT — see LICENSE.

Documentation

Overview

Package npmgo installs npm dependencies from a package-lock.json into a node_modules directory. It is pure Go with no external dependencies: parse the lock file, download tarballs from the registry (or a local content-addressed cache), verify their SHA-512 integrity, and extract them to disk.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Install

func Install(opts Options) error

Install reads the lock file referenced by opts, then resolves every (non-skipped) package: packages already installed at the correct version are skipped, otherwise the tarball is taken from the cache or downloaded, verified, cached, and extracted into opts.TargetDir.

Optional-dependency failures are logged and tolerated; any other package failure aborts the install and is returned.

Types

type CachedPackage added in v0.2.0

type CachedPackage struct {
	Name        string // import name, e.g. "react" or "@scope/pkg"
	Version     string
	TarballPath string // path to the .tgz in the cache
	Integrity   string
}

CachedPackage describes one package whose tarball lives in the cache but is not extracted to a node_modules directory.

type Options

type Options struct {
	LockfilePath string // default: "package-lock.json"
	TargetDir    string // default: "node_modules"
	CacheDir     string // default: "<user cache>/npmgo"; cache is keyed by integrity hash
	Registry     string // default: "https://registry.npmjs.org"
	Token        string // optional Bearer token for private registries
	Concurrency  int    // default: 8
	Production   bool   // skip devDependencies when true
	CacheOnly    bool   // download tarballs to cache without extracting (see CachePackages)
	Timeout      time.Duration

	// Logf, when set, receives human-readable progress lines, including
	// per-package cache/download/skip status. It may be called
	// concurrently and must be safe for concurrent use.
	Logf func(format string, args ...any)
}

Options configures an Install.

type Package

type Package struct {
	Path         string            // install path, e.g. "node_modules/react"
	Name         string            // published registry name; differs from the install path basename for npm aliases (install "string-width-cjs" -> published "string-width"). Empty when it matches.
	Version      string            // resolved version
	Resolved     string            // tarball URL
	Integrity    string            // subresource integrity, e.g. "sha512-..."
	Dependencies map[string]string // declared dependencies (informational)
	Dev          bool              // development-only dependency
	Optional     bool              // optional dependency (failure is non-fatal)
}

Package is a single resolved dependency that must be downloaded and extracted. Path is the install location relative to the project root, e.g. "node_modules/react" or "node_modules/a/node_modules/b".

func ParseLockfile

func ParseLockfile(data []byte) ([]Package, error)

ParseLockfile parses package-lock.json bytes into the set of packages that must be downloaded. It supports lockfileVersion 1, 2 and 3.

The v2/v3 "packages" map is preferred when present; otherwise the legacy v1 "dependencies" tree is walked. The root entry, and any file:/link: local references, are skipped.

func ParseLockfileFile

func ParseLockfileFile(path string) ([]Package, error)

ParseLockfileFile reads and parses a package-lock.json from disk.

type PackageIndex added in v0.2.0

type PackageIndex struct {
	Packages map[string]CachedPackage
	// contains filtered or unexported fields
}

PackageIndex maps import names to their cached tarballs and lazily opens (and caches) a TarballReader per package for module resolution. It is safe for concurrent use.

func CachePackages added in v0.2.0

func CachePackages(opts Options) (*PackageIndex, error)

CachePackages parses the lock file and ensures every (non-skipped) package tarball is present in the cache, downloading and verifying as needed. It never extracts to disk and creates no node_modules directory. The returned PackageIndex maps each package's import name to its cached tarball.

CachePackages forces CacheOnly behaviour regardless of opts.CacheOnly.

func (*PackageIndex) Reader added in v0.2.0

func (idx *PackageIndex) Reader(name string) (*TarballReader, error)

Reader returns the (cached) TarballReader for a package by import name.

func (*PackageIndex) ResolveImport added in v0.2.0

func (idx *PackageIndex) ResolveImport(spec, importerPkg, importerDir string) (pkg, file string, ok bool)

ResolveImport resolves an import specifier to a concrete file inside a cached package. For a bare specifier ("react", "react/jsx-runtime", "@scope/pkg") the entry is resolved from the target package's package.json. For a relative specifier ("./x", "../y") it is resolved within the importer's package (importerPkg / importerDir).

It returns the resolved package import name, the package-relative file path, and whether resolution succeeded. A miss (e.g. an unknown package) returns ok=false so the caller can fall back to default resolution.

func (*PackageIndex) ResolvePackageImport added in v0.3.0

func (idx *PackageIndex) ResolvePackageImport(spec, importerPkg string) (pkg, file string, ok bool)

ResolvePackageImport resolves a Node.js internal import specifier (one starting with "#") against the importing package's package.json "imports" map, returning a file within that same package. A miss returns ok=false.

type PackageJSON added in v0.2.0

type PackageJSON struct {
	Name    string          `json:"name"`
	Version string          `json:"version"`
	Main    string          `json:"main"`
	Module  string          `json:"module"`
	Exports json.RawMessage `json:"exports"`
	Imports json.RawMessage `json:"imports"`
	Browser json.RawMessage `json:"browser"`
}

PackageJSON holds the subset of package.json fields needed for module resolution. Exports and Browser are kept raw because their shapes vary (string, condition map, or subpath map) and are parsed on demand.

type RegistryConfig

type RegistryConfig struct {
	URL   string // base registry URL (used for relative-path fallbacks)
	Token string // optional Bearer token for private registries
}

RegistryConfig describes how to reach a package registry. Token, when set, is sent as a Bearer credential for private-registry access.

type TarballReader added in v0.2.0

type TarballReader struct {
	// contains filtered or unexported fields
}

TarballReader provides random read access to the files inside a cached npm .tgz without extracting it to disk. The archive is decompressed once on Open and indexed by package-relative path (the leading "package/" directory is stripped); subsequent ReadFile calls are in-memory lookups.

A TarballReader is safe for concurrent reads after Open returns: its contents are immutable.

func OpenTarball added in v0.2.0

func OpenTarball(path string) (*TarballReader, error)

OpenTarball reads and indexes the gzip-compressed tar at path.

func (*TarballReader) List added in v0.2.0

func (r *TarballReader) List() []string

List returns all package-relative file paths, sorted.

func (*TarballReader) ReadFile added in v0.2.0

func (r *TarballReader) ReadFile(name string) ([]byte, error)

ReadFile returns the contents of a single package-relative file. Leading "./" and "/" are normalized away, so "./index.js", "index.js" and "/index.js" are equivalent.

func (*TarballReader) ReadPackageJSON added in v0.2.0

func (r *TarballReader) ReadPackageJSON() (*PackageJSON, error)

ReadPackageJSON reads and parses the package's package.json.

Directories

Path Synopsis
cmd
npmgo command
Command npmgo is a minimal, pure-Go npm package installer driven by package-lock.json.
Command npmgo is a minimal, pure-Go npm package installer driven by package-lock.json.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL