builds dependency list successfully I think
parent
6b5ab437c4
commit
367880643b
|
|
@ -1 +1,4 @@
|
|||
.bin
|
||||
.bin
|
||||
node_modules
|
||||
.vscode
|
||||
package-lock.json
|
||||
48
lib/api.go
48
lib/api.go
|
|
@ -1,28 +1,15 @@
|
|||
package lib
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type PackageResponse struct {
|
||||
Name string `json:"name"`
|
||||
Versions map[string]Version `json:"versions"`
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
VersionNumber string `json:"version"`
|
||||
ID string `json:"_id"`
|
||||
Dependencies map[string]string `json:"dependencies"`
|
||||
DevDependencies map[string]string `json:"devDependencies"`
|
||||
OptionalDependencies map[string]string `json:"optionalDependencies"`
|
||||
Dist struct {
|
||||
Sum string `json:"shasum"`
|
||||
Tarball string `json:"tarball"`
|
||||
}
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Versions map[string]PackageRelease `json:"versions"`
|
||||
}
|
||||
|
||||
func GetPackageInfo(name string) (PackageResponse, error) {
|
||||
|
|
@ -48,16 +35,21 @@ func GetPackageInfo(name string) (PackageResponse, error) {
|
|||
return pr, nil
|
||||
}
|
||||
|
||||
func DownloadPackageTarball(sum, tarballURL string) (*tar.Reader, io.Closer, error) {
|
||||
resp, err := http.Get(tarballURL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not get tarball: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, nil, fmt.Errorf("Got a non 200 status code '%s': %v", resp.Status, resp.StatusCode)
|
||||
}
|
||||
|
||||
reader := tar.NewReader(resp.Body)
|
||||
return reader, resp.Body, nil
|
||||
type PackageRelease struct {
|
||||
Name string `json:"name"`
|
||||
VersionNumber string `json:"version"`
|
||||
ID string `json:"_id"`
|
||||
Dependencies map[string]string `json:"dependencies"`
|
||||
DevDependencies map[string]string `json:"devDependencies"`
|
||||
OptionalDependencies map[string]string `json:"optionalDependencies"`
|
||||
Dist Distribution `json:"dist"`
|
||||
}
|
||||
|
||||
func (v PackageRelease) TarURL() string {
|
||||
return fmt.Sprintf("https://registry.npmjs.org/%s/-/%[1]s-%s.tgz", v.Name, v.VersionNumber)
|
||||
}
|
||||
|
||||
type Distribution struct {
|
||||
Sum string `json:"shasum"`
|
||||
Tarball string `json:"tarball"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
package lib
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetPackageInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
inputName string
|
||||
VersionCountExpected int
|
||||
want PackageResponse
|
||||
wantVersion string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "testcafe-browser-provider-puppeteer",
|
||||
inputName: "testcafe-browser-provider-puppeteer",
|
||||
VersionCountExpected: 8,
|
||||
wantVersion: "1.0.1",
|
||||
want: PackageResponse{
|
||||
Name: "testcafe-browser-provider-puppeteer",
|
||||
Versions: map[string]PackageRelease{
|
||||
"1.0.1": PackageRelease{
|
||||
Name: "testcafe-browser-provider-puppeteer",
|
||||
VersionNumber: "1.0.1",
|
||||
ID: "testcafe-browser-provider-puppeteer@1.0.1",
|
||||
Dependencies: map[string]string{
|
||||
"babel-runtime": "^6.11.6",
|
||||
"puppeteer": "^0.10.2",
|
||||
},
|
||||
DevDependencies: map[string]string{
|
||||
"babel-eslint": "^6.1.2",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"babel-plugin-transform-runtime": "^6.12.0",
|
||||
"babel-preset-es2015": "^6.13.2",
|
||||
"babel-preset-es2015-loose": "^7.0.0",
|
||||
"babel-preset-stage-3": "^6.11.0",
|
||||
"del": "^2.0.0",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-babel": "^6.1.2",
|
||||
"gulp-eslint": "^3.0.1",
|
||||
"gulp-mocha": "^3.0.1",
|
||||
"node-version": "^1.0.0",
|
||||
"publish-please": "^2.1.4",
|
||||
},
|
||||
Dist: Distribution{
|
||||
Sum: "5bd7be2a401aec28fe9bef0b9aa9886bd56fd811",
|
||||
Tarball: "https://registry.npmjs.org/testcafe-browser-provider-puppeteer/-/testcafe-browser-provider-puppeteer-1.0.1.tgz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := GetPackageInfo(tt.inputName)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetPackageInfo() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if len(got.Versions) != tt.VersionCountExpected {
|
||||
t.Errorf("got %d, expected %d", len(got.Versions), tt.VersionCountExpected)
|
||||
}
|
||||
|
||||
wantVersion := tt.want.Versions[tt.wantVersion]
|
||||
gotVersion, gotOk := got.Versions[tt.wantVersion]
|
||||
|
||||
if !gotOk {
|
||||
t.Errorf("could not find version in response: %s", tt.wantVersion)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(gotVersion, wantVersion) {
|
||||
t.Errorf("-----\ngot: \t%+v\nwant:\t%+v", gotVersion, wantVersion)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Binary file not shown.
178
lib/deptree.go
178
lib/deptree.go
|
|
@ -1,120 +1,102 @@
|
|||
package lib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
)
|
||||
|
||||
type DependencyNode struct {
|
||||
Version
|
||||
Name string
|
||||
ResolvedDependencies []*DependencyNode
|
||||
ResolvedDevDependencies []*DependencyNode
|
||||
Error error
|
||||
}
|
||||
|
||||
func BuildDependencyTree(rp RawPackage) (*DependencyNode, error) {
|
||||
resolved := map[string]*DependencyNode{}
|
||||
root := &DependencyNode{
|
||||
Name: rp.Name,
|
||||
ResolvedDependencies: populateDepList(rp.Dependencies, resolved),
|
||||
ResolvedDevDependencies: populateDepList(rp.DevDependencies, resolved),
|
||||
func BuildDependencyTree(rp PackageJSON) (*DependencyList, error) {
|
||||
resolved := map[string]Dependency{}
|
||||
unresolved := []Dependency{
|
||||
Dependency{Name: rp.Name, Requires: rp.Dependencies},
|
||||
}
|
||||
|
||||
current := root
|
||||
for _, dep := range current.ResolvedDependencies {
|
||||
dep.ResolvedDependencies = populateDepList(dep.Dependencies, resolved)
|
||||
current = dep
|
||||
}
|
||||
for len(unresolved) > 0 {
|
||||
unresolvedDep := unresolved[0]
|
||||
unresolved = unresolved[1:len(unresolved)]
|
||||
|
||||
return root, nil
|
||||
}
|
||||
|
||||
func populateDepList(dependencies map[string]string, resolved map[string]*DependencyNode) []*DependencyNode {
|
||||
depList := make([]*DependencyNode, 0, len(dependencies))
|
||||
for name, versionConstraint := range dependencies {
|
||||
var node *DependencyNode
|
||||
var err error
|
||||
if resolvedDep, ok := resolved[name]; ok {
|
||||
c, err := semver.NewConstraint(versionConstraint)
|
||||
for k, p := range unresolvedDep.Requires {
|
||||
meta, err := GetPackageInfo(k)
|
||||
if err != nil {
|
||||
node = &DependencyNode{
|
||||
Name: name,
|
||||
Error: fmt.Errorf("could not create a constraint: %w", err),
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := semver.NewConstraint(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var selectedRelease PackageRelease
|
||||
var max *semver.Version
|
||||
for r, release := range meta.Versions {
|
||||
v, err := semver.NewVersion(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.Check(v) && (max == nil || v.GreaterThan(max)) {
|
||||
selectedRelease = release
|
||||
max = v
|
||||
}
|
||||
}
|
||||
|
||||
v, err := semver.NewVersion(resolvedDep.VersionNumber)
|
||||
if err != nil {
|
||||
node = &DependencyNode{
|
||||
Name: name,
|
||||
Error: fmt.Errorf("could not detect version for a dist: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
valid, errs := c.Validate(v)
|
||||
if len(errs) > 0 {
|
||||
node = &DependencyNode{
|
||||
Name: name,
|
||||
Error: fmt.Errorf("could not parse version '%s': %+v", resolvedDep.VersionNumber, errs),
|
||||
}
|
||||
} else if valid {
|
||||
node = resolvedDep
|
||||
}
|
||||
} else if strings.HasPrefix(versionConstraint, "github:") {
|
||||
// skip github packages for now
|
||||
node = &DependencyNode{
|
||||
Name: name,
|
||||
Error: errors.New("github package sources not supported."),
|
||||
}
|
||||
} else {
|
||||
node, err = createNpmDependencyNode(name, versionConstraint)
|
||||
if err != nil {
|
||||
node = &DependencyNode{Name: name, Error: err}
|
||||
} else {
|
||||
resolved[node.Name] = node
|
||||
}
|
||||
unresolved = append(unresolved, Dependency{
|
||||
Name: selectedRelease.Name,
|
||||
Version: selectedRelease.VersionNumber,
|
||||
Integrity: selectedRelease.Dist.Sum,
|
||||
Resolved: selectedRelease.Dist.Tarball,
|
||||
Requires: selectedRelease.Dependencies,
|
||||
})
|
||||
}
|
||||
|
||||
depList = append(depList, node)
|
||||
// don't add root dep to the resolve list
|
||||
if unresolvedDep.Name == rp.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
resolved[unresolvedDep.Name] = unresolvedDep
|
||||
}
|
||||
|
||||
return depList
|
||||
return &DependencyList{
|
||||
Name: rp.Name,
|
||||
Version: rp.Version,
|
||||
LockfileVersion: 1,
|
||||
Requires: true,
|
||||
Dependencies: resolved,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createNpmDependencyNode(name, versionConstraint string) (*DependencyNode, error) {
|
||||
|
||||
pinfo, err := GetPackageInfo(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get package information for '%s': %w", name, err)
|
||||
}
|
||||
|
||||
c, err := semver.NewConstraint(versionConstraint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create a constraint: %w", err)
|
||||
}
|
||||
|
||||
var max *semver.Version
|
||||
var selectedDist Version
|
||||
for version, dist := range pinfo.Versions {
|
||||
v, err := semver.NewVersion(version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not detect version for a dist: %w", err)
|
||||
}
|
||||
|
||||
valid, errs := c.Validate(v)
|
||||
if len(errs) > 0 {
|
||||
return nil, fmt.Errorf("could not parse version '%s': %w", version, err)
|
||||
}
|
||||
|
||||
if valid && v.GreaterThan(max) {
|
||||
max = v
|
||||
selectedDist = dist
|
||||
}
|
||||
}
|
||||
|
||||
return &DependencyNode{Name: name, Version: selectedDist}, nil
|
||||
type Dependency struct {
|
||||
Name string
|
||||
Version string
|
||||
Integrity string
|
||||
Resolved string
|
||||
Requires RequireList
|
||||
}
|
||||
|
||||
func (d Dependency) TarURL() string {
|
||||
return fmt.Sprintf("https://registry.npmjs.org/%s/-/%[1]s-%s.tgz", d.Name, d.Version)
|
||||
}
|
||||
|
||||
func (d Dependency) String() string {
|
||||
header := fmt.Sprintf("%s@%s - %s\n", d.Name, d.Version, d.Integrity)
|
||||
buf := bytes.NewBufferString(header)
|
||||
|
||||
for k, v := range d.Requires {
|
||||
fmt.Fprintf(buf, "\t%s => %s\n", k, v)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type RequireList map[string]string
|
||||
|
||||
type DependencyList struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
LockfileVersion int `json:"lockfileVersion"`
|
||||
Requires bool `json:"requires"`
|
||||
Dependencies map[string]Dependency `json:"dependencies"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,69 @@
|
|||
package lib
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_populateDepList(t *testing.T) {
|
||||
type Input struct {
|
||||
dependencies map[string]string
|
||||
resolved map[string]*DependencyNode
|
||||
}
|
||||
const simplePackageJSON = `
|
||||
{
|
||||
"name": "test",
|
||||
"version": "1.0.0",
|
||||
"scripts": { "test": "echo testing", "build": "echo hello > world.txt" },
|
||||
"keywords": [],
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": { "globby": "1.0.0" },
|
||||
"devDependencies": { "mocha": "*" }
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
func init() {
|
||||
log.SetFlags(log.Lshortfile)
|
||||
}
|
||||
|
||||
func TestBuildDependencyTree(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args Input
|
||||
want []*DependencyNode
|
||||
name string
|
||||
args PackageJSON
|
||||
depCount int
|
||||
want map[string]Dependency
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "resolve one package",
|
||||
args: Input{
|
||||
dependencies: map[string]string{"lodash": "4.17.15"},
|
||||
resolved: map[string]*DependencyNode{},
|
||||
name: "globby",
|
||||
depCount: 13,
|
||||
wantErr: false,
|
||||
args: PackageJSON{
|
||||
Name: "globby",
|
||||
Dependencies: map[string]string{
|
||||
"array-differ": "^1.0.0",
|
||||
"array-union": "^1.0.1",
|
||||
"async": "^0.9.0",
|
||||
"glob": "^4.0.2",
|
||||
},
|
||||
},
|
||||
want: []*DependencyNode{
|
||||
&DependencyNode{
|
||||
Name: "lodash",
|
||||
Error: nil,
|
||||
Version: Version{
|
||||
ID: "lodash-4.17.15",
|
||||
VersionNumber: "4.17.15",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webpack test",
|
||||
depCount: 13,
|
||||
wantErr: false,
|
||||
args: PackageJSON{
|
||||
Name: "webpack",
|
||||
Dependencies: map[string]string{
|
||||
"webpack": "*",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "react test",
|
||||
depCount: 13,
|
||||
wantErr: false,
|
||||
args: PackageJSON{
|
||||
Name: "website",
|
||||
Dependencies: map[string]string{
|
||||
"react": "16",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -36,32 +71,18 @@ func Test_populateDepList(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := populateDepList(tt.args.dependencies, tt.args.resolved)
|
||||
if len(got) != len(tt.want) {
|
||||
t.Errorf("len(populateDepList()) = (%v), want (%v)", len(got), len(tt.want))
|
||||
}
|
||||
for _, gotDep := range got {
|
||||
found := false
|
||||
for _, wantDep := range tt.want {
|
||||
if gotDep.ID == wantDep.ID {
|
||||
found = true
|
||||
} else {
|
||||
t.Logf("got: %+v\nwant:%+v", *gotDep, *wantDep)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(*gotDep, *wantDep) {
|
||||
t.Errorf("got %v, want %v", *gotDep, *wantDep)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("not found")
|
||||
|
||||
}
|
||||
got, err := BuildDependencyTree(tt.args)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("BuildDependencyTree() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("%+v", got)
|
||||
|
||||
if len(got.Dependencies) != tt.depCount {
|
||||
t.Errorf("BuildDependencyTree() = %v, want %v", len(got.Dependencies), tt.depCount)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,29 +2,29 @@ package lib
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type RawPackage struct {
|
||||
type PackageJSON struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Dependencies map[string]string `json:"dependencies"`
|
||||
DevDependencies map[string]string `json:"devDependencies"`
|
||||
}
|
||||
|
||||
func ParsePackageJson(path string) (RawPackage, error) {
|
||||
func ParsePackageJson(path string) (PackageJSON, error) {
|
||||
f, err := os.Open(path)
|
||||
if os.IsNotExist(err) {
|
||||
return RawPackage{}, errors.New("file doesn't exist.")
|
||||
return PackageJSON{}, os.ErrNotExist
|
||||
} else if err != nil {
|
||||
return RawPackage{}, fmt.Errorf("could not read file: %v", err)
|
||||
return PackageJSON{}, fmt.Errorf("could not read file: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var p RawPackage
|
||||
var p PackageJSON
|
||||
if err := json.NewDecoder(f).Decode(&p); err != nil {
|
||||
return RawPackage{}, fmt.Errorf("could not parse package.json file: %v", err)
|
||||
return PackageJSON{}, fmt.Errorf("could not parse package.json file: %v", err)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
|
|
|
|||
1
main.go
1
main.go
|
|
@ -9,6 +9,7 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Lshortfile)
|
||||
if len(os.Args) < 2 || os.Args[1] != "install" {
|
||||
log.Fatal("gpm [install]")
|
||||
}
|
||||
|
|
|
|||
30
package.json
30
package.json
|
|
@ -1,36 +1,16 @@
|
|||
{
|
||||
"name": "la-e2e",
|
||||
"name": "test",
|
||||
"version": "1.0.0",
|
||||
"description": "Test Cafe based End to End integration tests",
|
||||
"main": "smoke.js",
|
||||
"scripts": {
|
||||
"smoke": "testcafe puppeteer testCafeTests/smoke.js -S takeOnFail=true,fullPage=true -r spec,xunit:./test-results/xunit/smoke.xml ",
|
||||
"regressions": "testcafe puppeteer testCafeTests/regressions.js -S takeOnFail=true,fullPage=true -r spec,xunit:./test-results/xunit/regressions.xml ",
|
||||
"run-suite": "testcafe chrome:headless -e -r spec,xunit:./test-results/xunit/regressions.xml ",
|
||||
"debug-smoke": "testcafe chrome testCafeTests/smoke.js --debug-on-fail --sf",
|
||||
"smoke-selenium": "testcafe selenium:chrome -c 10 -q testCafeTests/smoke.js --ports 1337,1338 -e -r spec,json:./test-results/json/smoke.json,xunit:./test-results/xunit/smoke.xml --hostname",
|
||||
"regressions-selenium": "testcafe selenium:chrome -c 10 -q testCafeTests/regressions.js -e -S takeOnFail=true,fullPage=true --ports 1337,1338 -r spec,json:./test-results/json/regressions.json,xunit:./test-results/xunit/regressions.xml --hostname",
|
||||
"test-selenium": "testcafe selenium:chrome -c 10 -q chrome:headless testCafeTests/*.js -e -S takeOnFail=true,fullPage=true -r spec,json:./test-results/json/all.json,xunit:./test-results/xunit/all.xml --hostname",
|
||||
"docker-build": "docker build --no-cache -t testcafe .",
|
||||
"docker-dev": "docker run -it --rm --name tc --device /dev/net/tun -v $PWD:/opt/lae2e/:Z --cap-add=NET_ADMIN --cap-add=SYS_ADMIN --privileged -e UPDATE_SLACK=true -e RESULTS_SERVER=opsbot-local.liveauctioneers.com --dns 192.168.231.1 --entrypoint=bash testcafe",
|
||||
"live-bidders": "testcafe -c 4 chrome:headless testCafeTests/livebidding.js -f \"Live bidding- two bidders\" -e",
|
||||
"live-tests-visual": "testcafe chrome testCafeTests/livebidding.js -f \"Live bidding\" -e"
|
||||
"test": "echo testing",
|
||||
"build": "echo hello > world.txt"
|
||||
},
|
||||
"keywords": [],
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"base64-img": "^1.0.4",
|
||||
"core-js": "^3.6.4",
|
||||
"dotenv": "^8.2.0",
|
||||
"random-email": "github:jaronaearle/random-email",
|
||||
"request": "^2.88.0",
|
||||
"testcafe": "^1.8.1"
|
||||
"globby": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@liveauctioneers/eslint-config-la-react": "^0.1.4",
|
||||
"testcafe-browser-provider-puppeteer": "^1.5.2",
|
||||
"testcafe-browser-provider-selenium": "^0.4.0",
|
||||
"testcafe-reporter-json": "^2.2.0",
|
||||
"testcafe-reporter-xunit": "^2.1.0"
|
||||
"mocha": "*"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue