mesh-build-node: narrow the fetch to the named paths, build members against the SDK their lock names, and build, pack and publish a package (issues 1758, 1587 S15-S19; ADR 0499 §4, ADR 0498 §5, ADR 0500 §4-§6; lead decision L10)
build_node_fetch takes buildstep.FetchRequest (dir and paths narrow the workspace) and an
optional package {repository, commit, ref}: the group's package member, built and packed
shut off and published nowhere. Each member's lock decides its SDK: the group's packed
file when the version and integrity are the lock's, else the registry's (integrity
checked); a version the registry lacks fails waits for: @novox/mesh-sdk@<version>. A
package artifact is built by its own script and packed with npm pack, shut off; only a
trunk build publishes it, from this process with the write credential (never in a
workspace): absent is published, the same file is done, another is version taken:. The
registry configuration file now lies outside every workspace.
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// tgzOf is a packed npm package holding package/package.json of name@version and one more file.
|
||||
func tgzOf(t *testing.T, name, version string) []byte {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
zw := gzip.NewWriter(&buf)
|
||||
tw := tar.NewWriter(zw)
|
||||
for _, f := range [][2]string{{"package/package.json", `{"name":"` + name + `","version":"` + version + `"}`}, {"package/dist/index.js", "export const v = '" + version + "';\n"}} {
|
||||
n, body := f[0], f[1]
|
||||
tw.WriteHeader(&tar.Header{Name: n, Mode: 0o644, Size: int64(len(body)), Typeflag: tar.TypeReg})
|
||||
tw.Write([]byte(body))
|
||||
}
|
||||
tw.Close()
|
||||
zw.Close()
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// fakeRegistry serves the packument and the packed files of the versions it holds; asked counts its answers.
|
||||
func fakeRegistry(t *testing.T, m *Module, held map[string][]byte) *atomic.Int32 {
|
||||
t.Helper()
|
||||
var asked atomic.Int32
|
||||
var srv *httptest.Server
|
||||
srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
asked.Add(1)
|
||||
switch {
|
||||
case r.URL.Path == "/npm/@novox%2fmesh-sdk" || r.URL.Path == "/npm/@novox/mesh-sdk":
|
||||
if len(held) == 0 {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
versions := map[string]any{}
|
||||
for v, tgz := range held {
|
||||
versions[v] = map[string]any{"dist": map[string]any{"tarball": srv.URL + "/files/" + v + ".tgz", "integrity": integrityOf(tgz)}}
|
||||
}
|
||||
json.NewEncoder(w).Encode(map[string]any{"versions": versions})
|
||||
case strings.HasPrefix(r.URL.Path, "/files/"):
|
||||
v := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/files/"), ".tgz")
|
||||
if tgz, ok := held[v]; ok {
|
||||
w.Write(tgz)
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
m.Toolchain.Registry = func() (Registry, error) { return Registry{URL: srv.URL + "/npm/"}, nil }
|
||||
m.HTTP = srv.Client()
|
||||
return &asked
|
||||
}
|
||||
|
||||
// memberLocking is a member's directory whose lock names @novox/mesh-sdk@version with integrity (none when version is "").
|
||||
func memberLocking(t *testing.T, version, integrity string) string {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
lock := map[string]any{"lockfileVersion": 3, "packages": map[string]any{"": map[string]any{"name": "m"}}}
|
||||
if version != "" {
|
||||
lock["packages"].(map[string]any)["node_modules/@novox/mesh-sdk"] = map[string]any{"version": version, "integrity": integrity}
|
||||
}
|
||||
raw, _ := json.Marshal(lock)
|
||||
write(t, filepath.Join(dir, "package-lock.json"), string(raw), 0o644)
|
||||
return dir
|
||||
}
|
||||
|
||||
func placedVersion(dir string) string {
|
||||
raw, err := os.ReadFile(filepath.Join(dir, "node_modules", "@novox", "mesh-sdk", "package.json"))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
var p packageJSON
|
||||
json.Unmarshal(raw, &p)
|
||||
return p.Version
|
||||
}
|
||||
|
||||
// The lead's decision L10 (ADR 0500 §6): a member is built against the SDK version its own lock names, downloaded from
|
||||
// the registry and checked against the lock's integrity; a version the registry does not hold is a wait, `waits for:`.
|
||||
func TestAMemberGetsTheSDKVersionItsLockNamesOrWaitsForIt(t *testing.T) {
|
||||
m, _, _ := newMachine(t)
|
||||
held := tgzOf(t, sdkPackage, "0.1.15")
|
||||
fakeRegistry(t, m, map[string][]byte{"0.1.15": held})
|
||||
dir := memberLocking(t, "0.1.15", integrityOf(held))
|
||||
if err := m.placeSDK(context.Background(), dir, "modules/m", nil); err != nil || placedVersion(dir) != "0.1.15" {
|
||||
t.Errorf("the registry's 0.1.15: %v, placed %q", err, placedVersion(dir))
|
||||
}
|
||||
dir = memberLocking(t, "0.1.16", "sha512-x")
|
||||
err := m.placeSDK(context.Background(), dir, "modules/m", nil)
|
||||
if err == nil || err.Error() != delivery.WaitsFor+"@novox/mesh-sdk@0.1.16" {
|
||||
t.Errorf("a version the registry lacks answered %v, want %s@novox/mesh-sdk@0.1.16", err, delivery.WaitsFor)
|
||||
}
|
||||
dir = memberLocking(t, "0.1.15", "sha512-another")
|
||||
if err := m.placeSDK(context.Background(), dir, "modules/m", nil); err == nil || !strings.Contains(err.Error(), "sha512-another") {
|
||||
t.Errorf("a registry file of another integrity than the lock's answered %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ADR 0500 §5 (S3, S4): the group's packed package is served in place of the registry's when the member's lock names its
|
||||
// version with its integrity; another integrity, or a version neither the group nor the registry holds, fails naming both.
|
||||
// A member whose lock names no SDK keeps the toolchain's and asks the registry nothing.
|
||||
func TestTheGroupsPackageIsServedToAMemberLockingItsVersion(t *testing.T) {
|
||||
m, _, _ := newMachine(t)
|
||||
asked := fakeRegistry(t, m, map[string][]byte{"0.1.14": tgzOf(t, sdkPackage, "0.1.14")})
|
||||
tgz := tgzOf(t, sdkPackage, "0.1.15")
|
||||
group := &packed{Name: sdkPackage, Version: "0.1.15", Integrity: integrityOf(tgz), Digest: digestOf(tgz), tgz: tgz}
|
||||
dir := memberLocking(t, "0.1.15", group.Integrity)
|
||||
if err := m.placeSDK(context.Background(), dir, "modules/m", group); err != nil || placedVersion(dir) != "0.1.15" || asked.Load() != 0 {
|
||||
t.Errorf("the group's own version: %v, placed %q, the registry asked %d times", err, placedVersion(dir), asked.Load())
|
||||
}
|
||||
dir = memberLocking(t, "0.1.15", "sha512-other")
|
||||
if err := m.placeSDK(context.Background(), dir, "modules/m", group); err == nil || !strings.Contains(err.Error(), "sha512-other") || !strings.Contains(err.Error(), group.Integrity) {
|
||||
t.Errorf("another integrity answered %v, want both named", err)
|
||||
}
|
||||
dir = memberLocking(t, "0.1.14", integrityOf(tgzOf(t, sdkPackage, "0.1.14")))
|
||||
if err := m.placeSDK(context.Background(), dir, "modules/m", group); err != nil || placedVersion(dir) != "0.1.14" {
|
||||
t.Errorf("a member locking the registry's 0.1.14 in a group: %v, placed %q", err, placedVersion(dir))
|
||||
}
|
||||
dir = memberLocking(t, "0.1.13", "sha512-x")
|
||||
if err := m.placeSDK(context.Background(), dir, "modules/m", group); err == nil || !strings.Contains(err.Error(), "0.1.13") || !strings.Contains(err.Error(), "0.1.15") || strings.HasPrefix(err.Error(), delivery.WaitsFor) {
|
||||
t.Errorf("a version neither holds answered %v, want both named and no wait", err)
|
||||
}
|
||||
before := asked.Load()
|
||||
dir = memberLocking(t, "", "")
|
||||
if err := m.placeSDK(context.Background(), dir, "modules/m", group); err != nil || placedVersion(dir) != "" || asked.Load() != before {
|
||||
t.Errorf("a lock naming no SDK: %v, placed %q, the registry asked", err, placedVersion(dir))
|
||||
}
|
||||
}
|
||||
|
||||
// The fetch's `package` argument is a repository and a full commit, with a pull request's head at most; anything else is
|
||||
// refused before anything is fetched.
|
||||
func TestTheGroupPackageArgumentIsRefusedWhenItIsNotOne(t *testing.T) {
|
||||
for _, bad := range []any{"novox/mesh-sdk", map[string]any{"repository": "novox/mesh-sdk", "commit": "main"},
|
||||
map[string]any{"repository": "../x", "commit": strings.Repeat("a", 40)},
|
||||
map[string]any{"repository": "novox/mesh-sdk", "commit": strings.Repeat("a", 40), "ref": "refs/heads/main"}} {
|
||||
if _, err := groupPackageArg(bad); err == nil {
|
||||
t.Errorf("%v was taken", bad)
|
||||
}
|
||||
}
|
||||
g, err := groupPackageArg(map[string]any{"repository": "novox/mesh-sdk", "commit": strings.Repeat("a", 40), "ref": "refs/pull/9/head"})
|
||||
if err != nil || g.Ref != "refs/pull/9/head" {
|
||||
t.Errorf("a pull request's head: %+v %v", g, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user