Retention read no package-lock.json on the forge: in lockfile v2 and v3 a packages entry's dependencies is a map of name to range, which the reader decoded as nested entries, so all 32 files failed whole and the versions they pin were kept by nothing. Entries are now read one by one, tolerating unknown fields and skipping workspace links; an entry of an unknown shape is said, not the whole file. A dry run measured only the versions it would delete, so every kept version answered 0 bytes. Every version is now measured, the registry's total is answered, and a size that cannot be read is said (hq ADR 0251 §4).
199 lines
8.3 KiB
Go
199 lines
8.3 KiB
Go
package main
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
var knownHere = map[string]bool{"@novox/mesh-sdk": true, "@novox/ui": true, "sdk": true}
|
|
|
|
func pins(ps []Pinned) string {
|
|
var out []string
|
|
for _, p := range ps {
|
|
out = append(out, p.Package+"@"+p.Version)
|
|
}
|
|
sort.Strings(out)
|
|
return strings.Join(out, " ")
|
|
}
|
|
|
|
func TestAManifestNamesItsRegistryRangesAndNothingElse(t *testing.T) {
|
|
got, err := ReadManifest([]byte(`{
|
|
"dependencies": {"@novox/mesh-sdk": "^0.1.0", "left-pad": "^1.0.0", "@novox/ui": "file:../ui"},
|
|
"devDependencies": {"sdk": "npm:@novox/ui@~2.0.0"},
|
|
"peerDependencies": {"@novox/ui": "workspace:*"},
|
|
"optionalDependencies": {"@novox/ui": "1.x"}
|
|
}`), "r:package.json", knownHere)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var said []string
|
|
for _, w := range got {
|
|
said = append(said, w.Package+" "+w.Range)
|
|
}
|
|
sort.Strings(said)
|
|
if strings.Join(said, ",") != "@novox/mesh-sdk ^0.1.0,@novox/ui 1.x,@novox/ui ~2.0.0" {
|
|
t.Fatalf("read %v", said)
|
|
}
|
|
}
|
|
|
|
func TestAnNpmLockfileOfEveryVersionNamesWhatItPins(t *testing.T) {
|
|
v3 := `{"lockfileVersion":3,"packages":{"":{"name":"x"},
|
|
"node_modules/@novox/mesh-sdk":{"version":"0.1.3"},
|
|
"node_modules/a/node_modules/@novox/mesh-sdk":{"version":"0.1.1"},
|
|
"node_modules/sdk":{"name":"@novox/ui","version":"2.0.4"},
|
|
"node_modules/left-pad":{"version":"1.3.0"}}}`
|
|
got, err := ReadLock("package-lock.json", []byte(v3), "r:package-lock.json", knownHere)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if pins(got) != "@novox/mesh-sdk@0.1.1 @novox/mesh-sdk@0.1.3 @novox/ui@2.0.4" {
|
|
t.Fatalf("v3: %s", pins(got))
|
|
}
|
|
v1 := `{"lockfileVersion":1,"dependencies":{"@novox/mesh-sdk":{"version":"0.1.2","dependencies":{"@novox/ui":{"version":"1.0.0"}}},
|
|
"sdk":{"version":"npm:@novox/ui@2.0.1"}}}`
|
|
got, err = ReadLock("npm-shrinkwrap.json", []byte(v1), "r", knownHere)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if pins(got) != "@novox/mesh-sdk@0.1.2 @novox/ui@1.0.0 @novox/ui@2.0.1" {
|
|
t.Fatalf("v1: %s", pins(got))
|
|
}
|
|
if _, err := ReadLock("package-lock.json", []byte("{not json"), "r", knownHere); err == nil {
|
|
t.Fatal("a broken lockfile read as empty")
|
|
}
|
|
}
|
|
|
|
func TestAYarnLockOfEitherFormatNamesWhatItPins(t *testing.T) {
|
|
classic := "# yarn lockfile v1\n\n\"@novox/mesh-sdk@^0.1.0\", \"@novox/mesh-sdk@^0.1.2\":\n version \"0.1.4\"\n resolved \"x\"\n dependencies:\n left-pad \"^1\"\n\nleft-pad@^1:\n version \"1.3.0\"\n"
|
|
if got := pins(readYarnLock([]byte(classic), "r", knownHere)); got != "@novox/mesh-sdk@0.1.4" {
|
|
t.Fatalf("classic: %s", got)
|
|
}
|
|
berry := "__metadata:\n version: 6\n\n\"@novox/ui@npm:^2.0.0\":\n version: 2.0.7\n resolution: \"@novox/ui@npm:2.0.7\"\n"
|
|
if got := pins(readYarnLock([]byte(berry), "r", knownHere)); got != "@novox/ui@2.0.7" {
|
|
t.Fatalf("berry: %s", got)
|
|
}
|
|
}
|
|
|
|
func TestAPnpmLockNamesWhatItPinsInEachFormat(t *testing.T) {
|
|
lock := "lockfileVersion: '9.0'\npackages:\n '@novox/mesh-sdk@0.1.5':\n resolution: {}\n /@novox/ui/2.0.2:\n resolution: {}\n /@other/novox/ui@9.9.9:\n /x-sdk@1.0.0:\n"
|
|
if got := pins(readTextLock([]byte(lock), "r", knownHere)); got != "@novox/mesh-sdk@0.1.5 @novox/ui@2.0.2" {
|
|
t.Fatalf("pnpm: %s", got)
|
|
}
|
|
}
|
|
|
|
func TestOnlyTheRepositorysOwnManifestsAreRead(t *testing.T) {
|
|
for p, want := range map[string]bool{"package.json": true, "modules/x/package-lock.json": true, "yarn.lock": true,
|
|
"pnpm-lock.yaml": true, "node_modules/a/package.json": false, "README.md": false, "a/package.json.bak": false} {
|
|
if interesting(p) != want {
|
|
t.Errorf("%s: %v", p, !want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lockfiles as npm writes them: a `packages` entry's `dependencies` is a map of name to range, which a
|
|
// reader expecting nested entries cannot read (the shape every lockfile on the forge had).
|
|
|
|
const lockV1 = `{
|
|
"name": "app", "version": "1.0.0", "lockfileVersion": 1, "requires": true,
|
|
"dependencies": {
|
|
"@novox/mesh-sdk": {
|
|
"version": "0.1.2",
|
|
"resolved": "https://forge.example/api/packages/novox/npm/@novox/mesh-sdk/-/0.1.2/mesh-sdk-0.1.2.tgz",
|
|
"integrity": "sha512-x",
|
|
"requires": {"@novox/ui": "^1.0.0", "left-pad": "^1.3.0"},
|
|
"dependencies": {
|
|
"@novox/ui": {"version": "1.0.0", "resolved": "https://forge.example/x.tgz", "integrity": "sha512-y",
|
|
"requires": {"left-pad": "^1.0.0"},
|
|
"dependencies": {"@novox/mesh-sdk": {"version": "0.1.0", "requires": {}}}}
|
|
}
|
|
},
|
|
"left-pad": {"version": "1.3.0", "dev": true},
|
|
"sdk": {"version": "npm:@novox/ui@2.0.1"},
|
|
"local": {"version": "file:../local", "requires": {"@novox/ui": "*"}}
|
|
}
|
|
}`
|
|
|
|
const lockV2 = `{
|
|
"name": "app", "version": "1.0.0", "lockfileVersion": 2, "requires": true,
|
|
"packages": {
|
|
"": {"name": "app", "version": "1.0.0", "dependencies": {"@novox/mesh-sdk": "^0.1.0"},
|
|
"devDependencies": {"@novox/ui": "~1.1.0"}, "engines": {"node": ">=20"}},
|
|
"node_modules/@novox/mesh-sdk": {"version": "0.1.3", "resolved": "https://forge.example/a.tgz",
|
|
"integrity": "sha512-a", "dependencies": {"left-pad": "^1.3.0"}, "peerDependencies": {"@novox/ui": "*"},
|
|
"optionalDependencies": {"fsevents": "^2"}, "bin": {"sdk": "bin/sdk.js"}, "engines": {"node": ">=18"}},
|
|
"node_modules/@novox/ui": {"version": "1.1.4", "dev": true, "funding": [{"type": "x", "url": "y"}]},
|
|
"node_modules/left-pad": {"version": "1.3.0"}
|
|
},
|
|
"dependencies": {
|
|
"@novox/mesh-sdk": {"version": "0.1.3", "requires": {"left-pad": "^1.3.0"}},
|
|
"@novox/ui": {"version": "1.1.4", "dev": true},
|
|
"left-pad": {"version": "1.3.0"}
|
|
}
|
|
}`
|
|
|
|
const lockV3 = `{
|
|
"name": "app", "version": "1.0.0", "lockfileVersion": 3, "requires": true,
|
|
"packages": {
|
|
"": {"name": "app", "workspaces": ["packages/*"], "dependencies": {"@novox/mesh-sdk": "^0.1.0", "sdk": "npm:@novox/ui@^2.0.0"}},
|
|
"node_modules/@novox/mesh-sdk": {"version": "0.1.5", "license": "MIT", "dependencies": {"@novox/ui": "^1.0.0"}},
|
|
"node_modules/@novox/ui": {"version": "1.0.9", "devDependencies": {"typescript": "^5"}},
|
|
"node_modules/a/node_modules/@novox/mesh-sdk": {"version": "0.1.1", "dependencies": {"left-pad": "1.x"}},
|
|
"node_modules/a/node_modules/b/node_modules/@novox/ui": {"version": "1.0.2"},
|
|
"node_modules/sdk": {"name": "@novox/ui", "version": "2.0.4", "resolved": "https://forge.example/u.tgz"},
|
|
"node_modules/@novox/workspace-thing": {"resolved": "packages/thing", "link": true},
|
|
"node_modules/@novox/linked": {"resolved": "packages/linked", "link": true, "version": "9.9.9"},
|
|
"packages/thing": {"name": "@novox/workspace-thing", "version": "0.0.1", "dependencies": {"@novox/ui": "^1.0.0"}},
|
|
"node_modules/left-pad": {"version": "1.3.0", "peerDependencies": {"x": "*"}, "optionalDependencies": {"y": "1"}}
|
|
}
|
|
}`
|
|
|
|
var knownLocks = map[string]bool{"@novox/mesh-sdk": true, "@novox/ui": true, "@novox/workspace-thing": true, "@novox/linked": true}
|
|
|
|
func TestALockfileVersion1NamesEveryNestedVersion(t *testing.T) {
|
|
got, err := ReadLock("package-lock.json", []byte(lockV1), "r", knownLocks)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if pins(got) != "@novox/mesh-sdk@0.1.0 @novox/mesh-sdk@0.1.2 @novox/ui@1.0.0 @novox/ui@2.0.1" {
|
|
t.Fatalf("v1: %s", pins(got))
|
|
}
|
|
}
|
|
|
|
func TestALockfileVersion2ReadsBothHalves(t *testing.T) {
|
|
got, err := ReadLock("package-lock.json", []byte(lockV2), "r", knownLocks)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Each half names both; a version is kept once it is named, so naming it twice is harmless.
|
|
if pins(got) != "@novox/mesh-sdk@0.1.3 @novox/mesh-sdk@0.1.3 @novox/ui@1.1.4 @novox/ui@1.1.4" {
|
|
t.Fatalf("v2: %s", pins(got))
|
|
}
|
|
}
|
|
|
|
func TestALockfileVersion3ReadsPathsAliasesAndSkipsLinks(t *testing.T) {
|
|
got, err := ReadLock("package-lock.json", []byte(lockV3), "r", knownLocks)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := "@novox/mesh-sdk@0.1.1 @novox/mesh-sdk@0.1.5 @novox/ui@1.0.2 @novox/ui@1.0.9 @novox/ui@2.0.4"
|
|
if pins(got) != want {
|
|
t.Fatalf("v3: %s\nwant %s", pins(got), want)
|
|
}
|
|
}
|
|
|
|
func TestAnEntryOfAnUnknownShapeIsSaidAndTheRestIsRead(t *testing.T) {
|
|
lock := `{"lockfileVersion":3,"packages":{
|
|
"node_modules/@novox/ui":{"version":"1.0.0"},
|
|
"node_modules/@novox/mesh-sdk":{"version":{"odd":true}}},
|
|
"dependencies":{"@novox/ui":"not an object"}}`
|
|
got, err := ReadLock("package-lock.json", []byte(lock), "r", knownLocks)
|
|
if pins(got) != "@novox/ui@1.0.0" {
|
|
t.Fatalf("read %s", pins(got))
|
|
}
|
|
if err == nil || !strings.Contains(err.Error(), "2 entries not read") ||
|
|
!strings.Contains(err.Error(), `packages["node_modules/@novox/mesh-sdk"]`) {
|
|
t.Fatalf("got %v", err)
|
|
}
|
|
}
|