Bring node-tools' lock back in step with package.json, and fail the check when it falls behind (hq issue 624)
mesh/merge-gate head 52e275ad2f3a merged into trunk 954e1a53eef9 — pass, with a note: builds mesh-tools (+39 dependent(s)) → ace, g14, novox, shanks; no bu…
mesh/repo-check head 52e275ad2f3a merged into trunk 954e1a53eef9 — pass: its merge-check.sh passed
mesh/delivery delivered

The lab installs node-tools with npm ci, which refuses a lock that no longer
describes package.json; the SDK's range and the toolchain's development
dependencies had moved in package.json alone, so every lab run stopped at
setup. The image's build runs npm install and never read the lock, so nothing
noticed.
This commit is contained in:
jochen
2026-10-13 15:12:10 +02:00
parent 954e1a53ee
commit 52e275ad2f
3 changed files with 1948 additions and 2 deletions
@@ -0,0 +1,75 @@
package npmlock
import (
"os"
"path/filepath"
"strings"
"testing"
)
// TestNodeToolsLockIsInStepWithItsPackage: the lab installs node-tools with `npm ci`, which refuses a lock
// out of step with package.json, so a change to package.json without its lock fails here and not at a
// lab run's setup (novox/hq issue 624).
func TestNodeToolsLockIsInStepWithItsPackage(t *testing.T) {
out, err := Check(filepath.Join("..", ".."))
if err != nil {
t.Fatal(err)
}
if len(out) > 0 {
t.Fatalf("node-tools/package-lock.json is out of step with package.json; run `npm install --package-lock-only` in node-tools:\n%s", strings.Join(out, "\n"))
}
}
func TestALockBehindItsPackageIsNamed(t *testing.T) {
dir := t.TempDir()
write(t, dir, "package.json", `{"dependencies":{"@novox/mesh-sdk":"^0.1.6","nats":"^2.29.0"},"devDependencies":{"pg":"^8"}}`)
write(t, dir, "package-lock.json", `{"lockfileVersion":3,"packages":{
"":{"dependencies":{"@novox/mesh-sdk":"^0.1.0","nats":"^2.29.0","left":"^1"}},
"node_modules/@novox/mesh-sdk":{"version":"0.1.1"},
"node_modules/nats":{"version":"2.29.3"}}}`)
out, err := Check(dir)
if err != nil {
t.Fatal(err)
}
got := strings.Join(out, "\n")
for _, want := range []string{
`dependencies @novox/mesh-sdk is "^0.1.6" in package.json and "^0.1.0" in the lock`,
`dependencies left "^1" is in the lock and no longer in package.json`,
`devDependencies pg "^8" is in package.json and not in the lock`,
`devDependencies pg has no resolved package in the lock`,
} {
if !strings.Contains(got, want) {
t.Errorf("missing %q in:\n%s", want, got)
}
}
if len(out) != 4 {
t.Errorf("want 4 findings, got %d:\n%s", len(out), got)
}
}
func TestALockInStepIsQuiet(t *testing.T) {
dir := t.TempDir()
write(t, dir, "package.json", `{"dependencies":{"nats":"^2.29.0"}}`)
write(t, dir, "package-lock.json", `{"lockfileVersion":3,"packages":{"":{"dependencies":{"nats":"^2.29.0"}},"node_modules/nats":{"version":"2.29.3"}}}`)
out, err := Check(dir)
if err != nil || len(out) != 0 {
t.Fatalf("want quiet, got %v %v", out, err)
}
}
func TestAnOldLockfileVersionIsNamed(t *testing.T) {
dir := t.TempDir()
write(t, dir, "package.json", `{}`)
write(t, dir, "package-lock.json", `{"lockfileVersion":1,"dependencies":{}}`)
out, err := Check(dir)
if err != nil || len(out) != 1 {
t.Fatalf("want one finding, got %v %v", out, err)
}
}
func write(t *testing.T, dir, name, content string) {
t.Helper()
if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}