189 lines
4.4 KiB
Go
189 lines
4.4 KiB
Go
|
package npmproxy
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"crypto/sha512"
|
||
|
"encoding/base64"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"regexp"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/Masterminds/semver/v3"
|
||
|
"github.com/kataras/golog"
|
||
|
"github.com/mholt/archiver/v4"
|
||
|
"github.com/trading-peter/gowebbuild/fsutils"
|
||
|
)
|
||
|
|
||
|
func (p *Proxy) readPackageJson(pkgPath string) (PackageJson, error) {
|
||
|
pkgFile := filepath.Join(pkgPath, "package.json")
|
||
|
|
||
|
if !fsutils.IsFile(pkgFile) {
|
||
|
return PackageJson{}, fmt.Errorf("package.json not found in %s", pkgPath)
|
||
|
}
|
||
|
|
||
|
pkgData, err := os.ReadFile(pkgFile)
|
||
|
if err != nil {
|
||
|
return PackageJson{}, err
|
||
|
}
|
||
|
|
||
|
pkg := PackageJson{}
|
||
|
err = json.Unmarshal(pkgData, &pkg)
|
||
|
if err != nil {
|
||
|
return PackageJson{}, err
|
||
|
}
|
||
|
|
||
|
return pkg, nil
|
||
|
}
|
||
|
|
||
|
func (p *Proxy) findDependencyVersionConstraint(projectPkg PackageJson, pkgName string) (*semver.Constraints, error) {
|
||
|
if verStr, ok := projectPkg.Dependencies[pkgName]; ok {
|
||
|
return semver.NewConstraint(verStr)
|
||
|
}
|
||
|
|
||
|
return nil, fmt.Errorf("package %s not found in project dependencies", pkgName)
|
||
|
}
|
||
|
|
||
|
func (p *Proxy) findPackageSource(override *Override, pkgName string) (*Package, error) {
|
||
|
pkgNameParts := strings.Split(pkgName, "/")
|
||
|
pkgPath := filepath.Join(override.PackageRoot, pkgNameParts[len(pkgNameParts)-1])
|
||
|
|
||
|
// Read the projects package.json and figure out the requested semver version, which probably will be a contraint to assert against (like "^1.2.3").
|
||
|
projectPkg, err := p.readPackageJson(p.ProjectRoot)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
reqVersion, err := p.findDependencyVersionConstraint(projectPkg, pkgName)
|
||
|
|
||
|
pkg, err := p.readPackageJson(pkgPath)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
pkgVersion, err := semver.NewVersion(pkg.Version)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if !reqVersion.Check(pkgVersion) {
|
||
|
golog.Infof("Version %s in package sources for %s is not meeting the version constrains (%s) of the project. Forwarding request to upstream registry.", pkgVersion, pkgName, reqVersion)
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
pkgArchive, err := p.createPackage(pkgPath, pkg)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
integrity, shasum, err := p.createHashes(pkgArchive)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return &Package{
|
||
|
ID: pkg.Name,
|
||
|
Name: pkg.Name,
|
||
|
DistTags: DistTags{
|
||
|
Latest: pkg.Version,
|
||
|
},
|
||
|
Versions: map[string]Version{
|
||
|
pkg.Version: {
|
||
|
ID: pkg.Name,
|
||
|
Name: pkg.Name,
|
||
|
Version: pkg.Version,
|
||
|
Dependencies: pkg.Dependencies,
|
||
|
Dist: Dist{
|
||
|
Integrity: integrity,
|
||
|
Shasum: shasum,
|
||
|
Tarball: fmt.Sprintf("%s/files/%s", p.internalProxyUrl, filepath.Base(pkgArchive)),
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (p *Proxy) createPackage(pkgPath string, pkg PackageJson) (string, error) {
|
||
|
err := os.MkdirAll(p.pkgCachePath, 0755)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
pkgArchive := filepath.Join(p.pkgCachePath, sanitizePkgName(pkg.Name, pkg.Version)+".tar")
|
||
|
|
||
|
files, err := archiver.FilesFromDisk(nil, map[string]string{
|
||
|
pkgPath: ".",
|
||
|
})
|
||
|
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
filesFiltered := []archiver.File{}
|
||
|
|
||
|
filterRegex := regexp.MustCompile(`^node_modules|.git`)
|
||
|
|
||
|
for _, file := range files {
|
||
|
if filterRegex.MatchString(file.NameInArchive) {
|
||
|
continue
|
||
|
}
|
||
|
filesFiltered = append(filesFiltered, file)
|
||
|
}
|
||
|
|
||
|
out, err := os.Create(pkgArchive)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
defer out.Close()
|
||
|
|
||
|
format := archiver.CompressedArchive{
|
||
|
Archival: archiver.Tar{},
|
||
|
}
|
||
|
|
||
|
err = format.Archive(context.Background(), out, filesFiltered)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
return pkgArchive, nil
|
||
|
}
|
||
|
|
||
|
func (p *Proxy) createHashes(pkgArchive string) (string, string, error) {
|
||
|
// Open the file
|
||
|
file, err := os.Open(pkgArchive)
|
||
|
if err != nil {
|
||
|
return "", "", err
|
||
|
}
|
||
|
defer file.Close()
|
||
|
|
||
|
// Create a new SHA256 hash
|
||
|
hash := sha512.New()
|
||
|
|
||
|
// Copy the file data into the hash
|
||
|
if _, err := io.Copy(hash, file); err != nil {
|
||
|
return "", "", err
|
||
|
}
|
||
|
|
||
|
// Get the hash sum
|
||
|
hashSum := hash.Sum(nil)
|
||
|
|
||
|
// Generate the integrity string (SHA-256 base64-encoded)
|
||
|
integrity := "sha512-" + base64.StdEncoding.EncodeToString(hashSum)
|
||
|
|
||
|
// Generate the shasum (hexadecimal representation)
|
||
|
shasum := fmt.Sprintf("%x", hashSum)
|
||
|
|
||
|
return integrity, shasum, nil
|
||
|
}
|
||
|
|
||
|
// Replace all characters of the pages name that are not allowed in a URL with a hyphen.
|
||
|
func sanitizePkgName(pkgName string, version string) string {
|
||
|
pkgName = strings.ReplaceAll(pkgName, "@", "")
|
||
|
pkgName = strings.ReplaceAll(pkgName, "/", "_")
|
||
|
version = strings.ReplaceAll(version, ".", "_")
|
||
|
return fmt.Sprintf("%s_%s", pkgName, version)
|
||
|
}
|