A-C5: mesh-build-node builds against the latest published SDK, the toolchain pins none
Recovered from cfee5f2d (feat/47-G-buildmods). Builds: design 47 G §571 (the build writes a mesh package's lock entry from the latest published version), A-56 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -18,11 +18,12 @@ one changed byte of the lock or another recipe moves it, and typescript and esbu
|
||||
## The toolchain directory
|
||||
|
||||
`toolchain/package.json` and its committed `package-lock.json` name typescript 5.9.3, esbuild 0.25.12, @types/node 22.20.1 (the types tsc checks node imports against),
|
||||
@novox/mesh-sdk 0.1.14 and nats 2.29.3. The mesh unpacks both, as the archive artifact `toolchain`, into `<home>/toolchain/` (resource `toolchain-dir`; a
|
||||
and nats 2.29.3; it names no @novox/mesh-sdk: each TypeScript module is built against the latest release of the SDK the registry
|
||||
holds, placed in its own `node_modules` by the fetch whatever its lock names, or the group's tagged version (ADR 0573 §2, ADR 0607 §3). The mesh unpacks both, as the archive artifact `toolchain`, into `<home>/toolchain/` (resource `toolchain-dir`; a
|
||||
manifest names no host, and the lock does, so it is not a template); the program reads the lock from there. When `<cache dir>/toolchain/<recipe version>` is missing
|
||||
the module runs `npm ci --ignore-scripts` there once (a directory without its `.installed` mark counts as missing).
|
||||
|
||||
**How the registry is reached** (the choice of issue 1488): the lock holds @novox/mesh-sdk at the forge's public
|
||||
**How the registry is reached** (the choice of issue 1488): a lock names an @novox package at the forge's public
|
||||
address, and npm 12 installs a locked tarball only from the registry it is configured with. The module therefore
|
||||
requires `npm-package-registry` (provided by gitea, form none: reads are anonymous) and, like the lab module, takes its
|
||||
binding as a file in its home. The install gets a private npm config file with the `@novox` scope at the binding's
|
||||
|
||||
@@ -5,7 +5,8 @@ package main
|
||||
// TypeScript module in it the packages its package.json names beyond the SDK, installed from its committed lock.
|
||||
//
|
||||
// **Only what the module itself depends on is installed.** The SDK and nats come with the toolchain, so a bundle that
|
||||
// names only @novox/mesh-sdk (and build-time types and typescript) resolves nothing. One that names more must carry a
|
||||
// names only @novox/mesh-sdk (and build-time types and typescript) resolves nothing beyond the SDK, which is placed at
|
||||
// its latest published version (ADR 0573 §2). One that names more must carry a
|
||||
// package-lock.json, or the step fails naming its directory. The install is the build agent's (mesh-controller
|
||||
// internal/builder/dependencies.go:49-95): in a scratch copy of package.json and the lock with the SDK, devDependencies
|
||||
// and scripts taken out, `npm ci --omit=dev --omit=peer --ignore-scripts`, the install's copies of the SDK removed, and
|
||||
@@ -64,6 +65,9 @@ type FetchResult struct {
|
||||
// Group is the group's package the fetch packed and served to the TypeScript members (the call's `package`):
|
||||
// "<name>@<version> <integrity>".
|
||||
Group string `json:"group,omitempty"`
|
||||
// SDK is the version of @novox/mesh-sdk placed in each TypeScript module, by directory: the latest release, the
|
||||
// group's tagged version or the group's package (placeSDK; ADR 0573 §2).
|
||||
SDK map[string]string `json:"sdk,omitempty"`
|
||||
}
|
||||
|
||||
// narrowedMark is the file beside a workspace's tree saying the fetch narrowed it to a module's named paths.
|
||||
@@ -197,10 +201,14 @@ func (m *Module) fetchInto(ctx context.Context, req sdkstep.FetchRequest, group
|
||||
return nil, err
|
||||
}
|
||||
if refused == "" {
|
||||
// The SDK the module's own lock names, from the group or the registry (the lead's decision L10).
|
||||
// The latest published SDK, or the group's (ADR 0573 §2, ADR 0607 §3).
|
||||
if err := m.placeSDK(ctx, dir, d, served, group); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.SDK == nil {
|
||||
res.SDK = map[string]string{}
|
||||
}
|
||||
res.SDK[d] = placedSDK(dir)
|
||||
}
|
||||
if refused != "" {
|
||||
res.Refused = append(res.Refused, Refusal{Module: d, Reason: refused})
|
||||
@@ -368,7 +376,8 @@ func (m *Module) installOwn(ctx context.Context, root, dir, rel string) (install
|
||||
return true, "", os.RemoveAll(scratch)
|
||||
}
|
||||
|
||||
// removeSDKCopies removes every copy of the SDK the install pulled in: the toolchain's is the pin.
|
||||
// removeSDKCopies removes every copy of the SDK the install pulled in: the one placeSDK places, the latest published
|
||||
// or the group's, is the one the module is built against.
|
||||
func removeSDKCopies(nm string) error {
|
||||
var found []string
|
||||
err := filepath.WalkDir(nm, func(p string, d fs.DirEntry, err error) error {
|
||||
|
||||
@@ -97,7 +97,7 @@ func TestAModuleNamingOnlyTheSdkAndTypesInstallsNothing(t *testing.T) {
|
||||
}
|
||||
|
||||
// 1489/AC2: a module naming lodash with a lock gets npm ci --omit=dev --ignore-scripts (fake runner), its install put
|
||||
// at its node_modules without the SDK and without any copy of it; without a lock the step fails naming the directory.
|
||||
// at its node_modules without the install's SDK or any copy of it; without a lock the step fails naming the directory.
|
||||
func TestAModuleNamingLodashWithALockIsInstalledWithoutTheSdkAndWithoutALockFails(t *testing.T) {
|
||||
files := typeScriptModule(`{"dependencies":{"@novox/mesh-sdk":"^0.1.6","lodash":"^4"},"devDependencies":{"x":"1"},"scripts":{"test":"y"}}`)
|
||||
files["modules/m/package-lock.json"] = `{"lockfileVersion":3}`
|
||||
@@ -119,7 +119,8 @@ func TestAModuleNamingLodashWithALockIsInstalledWithoutTheSdkAndWithoutALockFail
|
||||
if _, err := os.Stat(filepath.Join(nm, "lodash", "index.js")); err != nil {
|
||||
t.Errorf("lodash is not at the module's node_modules: %v", err)
|
||||
}
|
||||
for _, gone := range []string{"@novox/mesh-sdk", "a/node_modules/@novox/mesh-sdk"} {
|
||||
// The install's copies are gone; the one at the top is the latest release placed after it (placeSDK).
|
||||
for _, gone := range []string{"@novox/mesh-sdk/index.js", "a/node_modules/@novox/mesh-sdk"} {
|
||||
if _, err := os.Stat(filepath.Join(nm, gone)); err == nil {
|
||||
t.Errorf("a copy of the SDK is left at %s", gone)
|
||||
}
|
||||
|
||||
@@ -161,8 +161,8 @@ func TestTheModuleCarriesItsToolchainLockAndTheManifestPlacesIt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 1488/AC1: the lock names the versions of the recipe: typescript 5.9.3, esbuild 0.25.12, @novox/mesh-sdk 0.1.14
|
||||
// and nats, all by the lock's own hash (read from the file the module carries).
|
||||
// 1488/AC1: the lock names the versions of the recipe: typescript 5.9.3, esbuild 0.25.12 and nats, all by the lock's
|
||||
// own hash (read from the file the module carries), and no @novox/mesh-sdk: the SDK is unpinned (ADR 0573 §2).
|
||||
func TestTheLockNamesTheToolchainVersions(t *testing.T) {
|
||||
raw, err := os.ReadFile(filepath.Join(moduleDir, "toolchain", "package-lock.json"))
|
||||
if err != nil {
|
||||
@@ -176,7 +176,7 @@ func TestTheLockNamesTheToolchainVersions(t *testing.T) {
|
||||
if err := json.Unmarshal(raw, &lock); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for pkg, want := range map[string]string{"typescript": "5.9.3", "esbuild": "0.25.12", "@novox/mesh-sdk": "0.1.14", "nats": "2.29.3", "@types/node": "22.20.1"} {
|
||||
for pkg, want := range map[string]string{"typescript": "5.9.3", "esbuild": "0.25.12", "@novox/mesh-sdk": "", "nats": "2.29.3", "@types/node": "22.20.1"} {
|
||||
if got := lock.Packages["node_modules/"+pkg].Version; got != want {
|
||||
t.Errorf("%s = %q in the lock, want %s", pkg, got, want)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -292,7 +293,7 @@ func (m *Module) packGroup(ctx context.Context, root string, run forgekey.Runner
|
||||
}
|
||||
|
||||
// sdkLocked is what a module's lock names of the SDK: its version, its integrity and where it was resolved from; ok is
|
||||
// false when the lock names none (the toolchain's SDK is then the module's, as before).
|
||||
// false when the lock names none (the latest release is then placed, placeSDK).
|
||||
func sdkLocked(dir string) (version, integrity string, ok bool, err error) {
|
||||
raw, err := os.ReadFile(filepath.Join(dir, "package-lock.json"))
|
||||
if os.IsNotExist(err) {
|
||||
@@ -317,21 +318,25 @@ func sdkLocked(dir string) (version, integrity string, ok bool, err error) {
|
||||
return p.Version, p.Integrity, true, nil
|
||||
}
|
||||
|
||||
// placeSDK places in the module's node_modules the SDK version its lock names (the lead's decision L10): the group's
|
||||
// packed package when it is that version (its integrity must be the lock's), else the registry's; a version neither
|
||||
// holds is a wait (`waits for:`) when no group package was given, and a failure naming both when one was. Given the
|
||||
// group's tagged version (tagged, ADR 0607 §3), that version is placed from the registry whatever the lock names, its
|
||||
// integrity the lock's when the lock names it, else the registry's own; one the registry lacks fails naming it.
|
||||
// placeSDK places in the module's node_modules the SDK it is built against: the latest release the registry holds,
|
||||
// whatever its lock names (novox/hq ADR 0573 §2: a module builds against the latest published version of each mesh
|
||||
// package; the lead's decision L10's version from the lock is gone), or the group's packed package when the lock names
|
||||
// its version. Every TypeScript module gets it, named in its package.json or not, since its launcher imports the SDK
|
||||
// (writeLaunchers) and the toolchain holds none. The lock's integrity is checked when the lock names the version placed;
|
||||
// a registry holding no release is a wait (`waits for:`) when no group package was given, and a failure naming the group
|
||||
// when one was. Given the group's tagged version (tagged, ADR 0607 §3), that version is placed from the registry
|
||||
// whatever the lock names, its integrity the lock's when the lock names it, else the registry's; one the registry lacks
|
||||
// fails naming it.
|
||||
func (m *Module) placeSDK(ctx context.Context, dir, rel string, group *packed, tagged *groupPackage) error {
|
||||
version, integrity, ok, err := sdkLocked(dir)
|
||||
if err != nil || !ok {
|
||||
version, integrity, _, err := sdkLocked(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var tgz []byte
|
||||
switch {
|
||||
case tagged.tagged():
|
||||
if version != tagged.Version {
|
||||
if m.Say != nil {
|
||||
if m.Say != nil && version != "" {
|
||||
m.Say("%s: its lock names %s@%s; the group's tagged version %s is placed (ADR 0607 §3)", rel, sdkPackage, version, tagged.Version)
|
||||
}
|
||||
version, integrity = tagged.Version, ""
|
||||
@@ -351,23 +356,39 @@ func (m *Module) placeSDK(ctx context.Context, dir, rel string, group *packed, t
|
||||
return fmt.Errorf("the registry's %s is %s, and %s names %s", want, integrityOf(got), rel, integrity)
|
||||
}
|
||||
tgz = got
|
||||
case group != nil && group.Name == sdkPackage && group.Version == version:
|
||||
case group != nil && group.Name == sdkPackage && version != "" && group.Version == version:
|
||||
if integrity != "" && integrity != group.Integrity {
|
||||
return fmt.Errorf("the lock of %s names %s with the integrity %s, and the group's package packs to %s", rel, sdkPackage+"@"+version, integrity, group.Integrity)
|
||||
}
|
||||
tgz = group.tgz
|
||||
default:
|
||||
latest, err := m.registryLatest(ctx, sdkPackage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if latest == "" {
|
||||
if group != nil {
|
||||
return fmt.Errorf("the registry holds no release of %s and the group's package is %s@%s, which the lock of %s does not name", sdkPackage, group.Name, group.Version, rel)
|
||||
}
|
||||
return fmt.Errorf("%s%s", builds.WaitsFor, sdkPackage)
|
||||
}
|
||||
if version != latest {
|
||||
if m.Say != nil && version != "" {
|
||||
m.Say("%s: its lock names %s@%s; the latest published %s is placed (ADR 0573 §2)", rel, sdkPackage, version, latest)
|
||||
}
|
||||
version, integrity = latest, ""
|
||||
}
|
||||
want := sdkPackage + "@" + version
|
||||
got, held, err := m.registryPackage(ctx, sdkPackage, version)
|
||||
got, registered, held, err := m.registryPackageChecked(ctx, sdkPackage, version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !held {
|
||||
if group != nil {
|
||||
return fmt.Errorf("the lock of %s names %s, which neither the registry nor the group (%s@%s) holds", rel, want, group.Name, group.Version)
|
||||
}
|
||||
return fmt.Errorf("%s%s", builds.WaitsFor, want)
|
||||
}
|
||||
if integrity == "" {
|
||||
integrity = registered
|
||||
}
|
||||
if integrity != "" && integrityOf(got) != integrity {
|
||||
return fmt.Errorf("the registry's %s is %s, and the lock of %s names %s", want, integrityOf(got), rel, integrity)
|
||||
}
|
||||
@@ -380,6 +401,47 @@ func (m *Module) placeSDK(ctx context.Context, dir, rel string, group *packed, t
|
||||
return unpackPackage(tgz, into)
|
||||
}
|
||||
|
||||
// placedSDK is the version of the SDK placed in the module's node_modules ("" when none is).
|
||||
func placedSDK(dir string) string {
|
||||
p, err := readPackageJSON(filepath.Join(dir, "node_modules", filepath.FromSlash(sdkPackage)))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return p.Version
|
||||
}
|
||||
|
||||
// registryLatest is the latest release of name the registry holds: the highest version with no pre-release, so a
|
||||
// group's tagged version is never taken for it; "" when the registry holds none.
|
||||
func (m *Module) registryLatest(ctx context.Context, name string) (string, error) {
|
||||
p, held, err := m.packumentOf(ctx, name)
|
||||
if err != nil || !held {
|
||||
return "", err
|
||||
}
|
||||
latest := ""
|
||||
for v := range p.Versions {
|
||||
if !npmVersionOK.MatchString(v) || strings.Contains(v, "-") {
|
||||
continue
|
||||
}
|
||||
if latest == "" || releaseLess(latest, v) {
|
||||
latest = v
|
||||
}
|
||||
}
|
||||
return latest, nil
|
||||
}
|
||||
|
||||
// releaseLess orders two releases (major.minor.patch, no pre-release) by their numbers.
|
||||
func releaseLess(a, b string) bool {
|
||||
pa, pb := strings.Split(a, "."), strings.Split(b, ".")
|
||||
for i := 0; i < 3; i++ {
|
||||
x, _ := strconv.Atoi(pa[i])
|
||||
y, _ := strconv.Atoi(pb[i])
|
||||
if x != y {
|
||||
return x < y
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// registryPackageChecked is registryPackage with the integrity the registry's document names of name@version.
|
||||
func (m *Module) registryPackageChecked(ctx context.Context, name, version string) (tgz []byte, integrity string, held bool, err error) {
|
||||
_, integrity, held, err = m.registryVersion(ctx, name, version)
|
||||
@@ -408,32 +470,41 @@ type packument struct {
|
||||
} `json:"versions"`
|
||||
}
|
||||
|
||||
// registryVersion reads what the registry holds of name@version: its packed file's address and integrity, and whether
|
||||
// it holds it at all.
|
||||
func (m *Module) registryVersion(ctx context.Context, name, version string) (tarball, integrity string, held bool, err error) {
|
||||
// packumentOf reads the registry's document of name; held is false when the registry has none.
|
||||
func (m *Module) packumentOf(ctx context.Context, name string) (packument, bool, error) {
|
||||
reg, err := m.Toolchain.Registry()
|
||||
if err != nil {
|
||||
return "", "", false, err
|
||||
return packument{}, false, err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reg.URL+url.PathEscape(name), nil)
|
||||
if err != nil {
|
||||
return "", "", false, err
|
||||
return packument{}, false, err
|
||||
}
|
||||
resp, err := m.httpClient().Do(req)
|
||||
if err != nil {
|
||||
return "", "", false, fmt.Errorf("asking the registry for %s: %w", name, err)
|
||||
return packument{}, false, fmt.Errorf("asking the registry for %s: %w", name, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return "", "", false, nil
|
||||
return packument{}, false, nil
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
said, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
|
||||
return "", "", false, fmt.Errorf("the registry answered %s for %s: %s", resp.Status, name, strings.TrimSpace(string(said)))
|
||||
return packument{}, false, fmt.Errorf("the registry answered %s for %s: %s", resp.Status, name, strings.TrimSpace(string(said)))
|
||||
}
|
||||
var p packument
|
||||
if err := json.NewDecoder(io.LimitReader(resp.Body, 64<<20)).Decode(&p); err != nil {
|
||||
return "", "", false, fmt.Errorf("the registry's document of %s: %w", name, err)
|
||||
return packument{}, false, fmt.Errorf("the registry's document of %s: %w", name, err)
|
||||
}
|
||||
return p, true, nil
|
||||
}
|
||||
|
||||
// registryVersion reads what the registry holds of name@version: its packed file's address and integrity, and whether
|
||||
// it holds it at all.
|
||||
func (m *Module) registryVersion(ctx context.Context, name, version string) (tarball, integrity string, held bool, err error) {
|
||||
p, held, err := m.packumentOf(ctx, name)
|
||||
if err != nil || !held {
|
||||
return "", "", false, err
|
||||
}
|
||||
v, ok := p.Versions[version]
|
||||
if !ok {
|
||||
|
||||
@@ -91,29 +91,42 @@ func placedVersion(dir string) string {
|
||||
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) {
|
||||
// ADR 0573 §2 (unpinned, the lead's decision L10's lock version gone): a member is built against the latest release the
|
||||
// registry holds, whatever its lock names or when it names none, checked against the lock's integrity only when the lock
|
||||
// names that version; a pre-release is never the latest; a registry holding no release is a wait, `waits for:`.
|
||||
func TestAMemberGetsTheLatestPublishedSDKWhateverItsLockNames(t *testing.T) {
|
||||
m, _, _ := newMachine(t)
|
||||
held := tgzOf(t, sdkPackage, "0.1.15")
|
||||
held, older, pre := tgzOf(t, sdkPackage, "0.1.15"), tgzOf(t, sdkPackage, "0.1.9"), tgzOf(t, sdkPackage, "0.1.16-feat-x.1")
|
||||
fakeRegistry(t, m, map[string][]byte{"0.1.15": held, "0.1.9": older, "0.1.16-feat-x.1": pre})
|
||||
for _, c := range []struct {
|
||||
name, locks, integrity, fails string
|
||||
}{
|
||||
{name: "a lock naming the latest", locks: "0.1.15", integrity: integrityOf(held)},
|
||||
{name: "a lock naming an older version (the pin)", locks: "0.1.9", integrity: integrityOf(older)},
|
||||
{name: "a lock naming a version the registry lacks", locks: "0.1.16", integrity: "sha512-x"},
|
||||
{name: "a lock naming none", locks: ""},
|
||||
{name: "a lock naming the latest with another integrity", locks: "0.1.15", integrity: "sha512-another", fails: "sha512-another"},
|
||||
} {
|
||||
dir := memberLocking(t, c.locks, c.integrity)
|
||||
err := m.placeSDK(context.Background(), dir, "modules/m", nil, nil)
|
||||
switch {
|
||||
case c.fails == "" && (err != nil || placedVersion(dir) != "0.1.15"):
|
||||
t.Errorf("%s: %v, placed %q, want the latest release 0.1.15", c.name, err, placedVersion(dir))
|
||||
case c.fails != "" && (err == nil || !strings.Contains(err.Error(), c.fails)):
|
||||
t.Errorf("%s: %v, want it to fail naming %s", c.name, err, c.fails)
|
||||
}
|
||||
}
|
||||
fakeRegistry(t, m, map[string][]byte{"0.1.16-feat-x.1": pre})
|
||||
if err := m.placeSDK(context.Background(), memberLocking(t, "0.1.15", ""), "modules/m", nil, nil); err == nil || err.Error() != builds.WaitsFor+"@novox/mesh-sdk" {
|
||||
t.Errorf("a registry with no release answered %v, want %s@novox/mesh-sdk", err, builds.WaitsFor)
|
||||
}
|
||||
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, 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, nil)
|
||||
if err == nil || err.Error() != builds.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, builds.WaitsFor)
|
||||
}
|
||||
dir = memberLocking(t, "0.1.15", "sha512-another")
|
||||
if err := m.placeSDK(context.Background(), dir, "modules/m", nil, nil); err == nil || !strings.Contains(err.Error(), "sha512-another") {
|
||||
t.Errorf("a registry file of another integrity than the lock's answered %v", err)
|
||||
}
|
||||
var dir string
|
||||
// ADR 0607 §3, §5 (design 51 *Tagged versions*): given the group's tagged version, the fetch places it from the
|
||||
// registry whatever the lock names, against the lock's integrity when the lock names it, else the registry's; one the
|
||||
// registry lacks fails naming it, no wait (the check waited for its published).
|
||||
tagged := tgzOf(t, sdkPackage, "0.1.16-feat-x.1")
|
||||
tagged := pre
|
||||
fakeRegistry(t, m, map[string][]byte{"0.1.15": held, "0.1.16-feat-x.1": tagged})
|
||||
group := &groupPackage{Name: sdkPackage, Version: "0.1.16-feat-x.1"}
|
||||
for _, c := range []struct {
|
||||
@@ -140,8 +153,8 @@ func TestAMemberGetsTheSDKVersionItsLockNamesOrWaitsForIt(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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.
|
||||
// version with its integrity; another integrity fails naming both. A member locking another version, or none, gets the
|
||||
// registry's latest release (ADR 0573 §2); with no release in the registry it fails naming the group's, no wait.
|
||||
func TestTheGroupsPackageIsServedToAMemberLockingItsVersion(t *testing.T) {
|
||||
m, _, _ := newMachine(t)
|
||||
asked := fakeRegistry(t, m, map[string][]byte{"0.1.14": tgzOf(t, sdkPackage, "0.1.14")})
|
||||
@@ -159,14 +172,15 @@ func TestTheGroupsPackageIsServedToAMemberLockingItsVersion(t *testing.T) {
|
||||
if err := m.placeSDK(context.Background(), dir, "modules/m", group, nil); 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, nil); err == nil || !strings.Contains(err.Error(), "0.1.13") || !strings.Contains(err.Error(), "0.1.15") || strings.HasPrefix(err.Error(), builds.WaitsFor) {
|
||||
t.Errorf("a version neither holds answered %v, want both named and no wait", err)
|
||||
for _, locks := range []string{"0.1.13", ""} {
|
||||
dir = memberLocking(t, locks, "sha512-x")
|
||||
if err := m.placeSDK(context.Background(), dir, "modules/m", group, nil); err != nil || placedVersion(dir) != "0.1.14" {
|
||||
t.Errorf("a member in a group locking %q: %v, placed %q, want the registry's latest 0.1.14", locks, err, placedVersion(dir))
|
||||
}
|
||||
}
|
||||
before := asked.Load()
|
||||
dir = memberLocking(t, "", "")
|
||||
if err := m.placeSDK(context.Background(), dir, "modules/m", group, nil); err != nil || placedVersion(dir) != "" || asked.Load() != before {
|
||||
t.Errorf("a lock naming no SDK: %v, placed %q, the registry asked", err, placedVersion(dir))
|
||||
fakeRegistry(t, m, nil)
|
||||
if err := m.placeSDK(context.Background(), memberLocking(t, "0.1.13", "sha512-x"), "modules/m", group, nil); err == nil || !strings.Contains(err.Error(), "0.1.15") || strings.HasPrefix(err.Error(), builds.WaitsFor) {
|
||||
t.Errorf("no release and a group answered %v, want the group's named and no wait", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
// RecipeSteps is the recipe's own version: changed whenever the steps of fetch, test or build change in a way that
|
||||
// can change what they produce (ADR 0405 §7). It is one of the two values the recipe version hashes.
|
||||
const RecipeSteps = "2" // 2: a member is built against the SDK its own lock names (R4e, the lead's decision L10)
|
||||
const RecipeSteps = "3" // 3: a member is built against the latest published SDK, the toolchain holds none (ADR 0573 §2)
|
||||
|
||||
// Recipe is every value that makes up the recipe version.
|
||||
type Recipe struct {
|
||||
|
||||
@@ -3,7 +3,7 @@ package main
|
||||
// A workspace and the packages a step runs against (novox/hq ADR 0469 §2 and §5; issues 1490, 1491). A workspace is
|
||||
// <work dir>/<step id>/tree, made by the fetch step. The toolchain's packages are copied to <work dir>/<step id>/
|
||||
// node_modules, which lies above the tree: node, tsc and esbuild resolve an import by walking up from the importing
|
||||
// file, so @novox/mesh-sdk and the types are found there while the tree itself, which a bundle is packed from, stays
|
||||
// file, so the types are found there while the tree itself, which a bundle is packed from, stays
|
||||
// what the commit holds. A copy and not a link, because the mesh makes no symlink.
|
||||
|
||||
import (
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"name": "mesh-build-node-toolchain",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@novox/mesh-sdk": "0.1.14",
|
||||
"nats": "2.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -459,11 +458,6 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@novox/mesh-sdk": {
|
||||
"version": "0.1.14",
|
||||
"resolved": "https://git.novox.be/api/packages/novox/npm/%40novox%2Fmesh-sdk/-/0.1.14/mesh-sdk-0.1.14.tgz",
|
||||
"integrity": "sha512-ICdIfVicQQXSw6fkLiwzoRx3weiWh0jsIEYG/m98CKgFIlziZzb7V+ZmT/TvPO9bTDFDtfqDLoFu+OXY5+ryQw=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"private": true,
|
||||
"description": "The Node build module's toolchain: what npm ci installs into <cache>/toolchain/<recipe version> (issue 1488).",
|
||||
"dependencies": {
|
||||
"@novox/mesh-sdk": "0.1.14",
|
||||
"nats": "2.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Reference in New Issue
Block a user