Ship the Node build module's toolchain lock as an archive, not a template naming hosts (issue 1488)

This commit is contained in:
2026-10-20 21:15:56 +00:00
parent 00fa5e74d0
commit 983a6c39c8
6 changed files with 52 additions and 38 deletions
+4 -4
View File
@@ -10,13 +10,13 @@ builder uses it. Its forge key and ssh files are the same as mesh-build-go's (is
`recipe.go` names the one Node version it builds with (`RecipeNode`, `v26.10.0`, as `node --version` prints it) and the
recipe's own version (`RecipeSteps`). The recipe version is a hash of those two and of the bytes of the lock file
`files/toolchain/package-lock.json`, so one changed byte of the lock, another Node or another recipe moves it.
`toolchain/package-lock.json`, so one changed byte of the lock, another Node or another recipe moves it.
## The toolchain directory
`files/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 writes both into `<home>/toolchain/` (resources `toolchain-package`
and `toolchain-lock`); the program reads the lock from there. When `<cache dir>/toolchain/<recipe version>` is missing
`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
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
@@ -24,7 +24,7 @@ func realMachine(t *testing.T) (*Module, string) {
}
home, cache, work := t.TempDir(), t.TempDir(), t.TempDir()
for _, n := range []string{"package.json", "package-lock.json"} {
raw, err := os.ReadFile(filepath.Join(moduleDir, "files", "toolchain", n))
raw, err := os.ReadFile(filepath.Join(moduleDir, "toolchain", n))
if err != nil {
t.Fatal(err)
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"os"
"path/filepath"
"regexp"
"slices"
"strings"
"testing"
@@ -124,22 +125,13 @@ func TestTheToolsServedAreTheToolsTheManifestNames(t *testing.T) {
}
}
// 1488/AC1: the module carries the committed lock beside its package.json, and the manifest has the mesh write both
// into the home's toolchain directory, where the program reads them.
// 1488/AC1: the module carries the committed lock beside its package.json, and the manifest unpacks them into the
// home's toolchain directory, where the program reads them.
func TestTheModuleCarriesItsToolchainLockAndTheManifestPlacesIt(t *testing.T) {
m := readManifest(t)
for _, name := range []string{"package.json", "package-lock.json"} {
if _, err := os.Stat(filepath.Join(moduleDir, "files", "toolchain", name)); err != nil {
t.Errorf("the module carries no files/toolchain/%s: %v", name, err)
}
}
lock := resource(t, m, "toolchain-lock")
if lock["path"] != "${dir:toolchain-dir}/package-lock.json" || lock["owner"] != "mesh-build-node" {
t.Errorf("toolchain-lock = %v", lock)
}
for _, r := range readForgeManifest(t).Resources {
if r["id"] == "toolchain-lock" && r["template"] != "files/toolchain/package-lock.json" {
t.Errorf("toolchain-lock takes its text from %v", r["template"])
if _, err := os.Stat(filepath.Join(moduleDir, "toolchain", name)); err != nil {
t.Errorf("the module carries no toolchain/%s: %v", name, err)
}
}
if !slices.Contains(m.Requires, "npm-package-registry") {
@@ -150,7 +142,7 @@ 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).
func TestTheLockNamesTheToolchainVersions(t *testing.T) {
raw, err := os.ReadFile(filepath.Join(moduleDir, "files", "toolchain", "package-lock.json"))
raw, err := os.ReadFile(filepath.Join(moduleDir, "toolchain", "package-lock.json"))
if err != nil {
t.Fatal(err)
}
@@ -168,3 +160,34 @@ func TestTheLockNamesTheToolchainVersions(t *testing.T) {
}
}
}
// 1488 (the catalogue's manifest check): a manifest names no host, the mesh being name-agnostic. The tree-read
// manifest carries each template's text as the resource's content, and the toolchain's lock names registries, so it is
// shipped as an archive artifact instead, never as a template. No resource of the module, read as the mesh reads it,
// carries content naming a host, and the raw module.json names none either.
func TestNoResourceCarriesContentNamingAHost(t *testing.T) {
host := regexp.MustCompile(`(?i)://[a-z0-9]|\b[a-z0-9-]+(\.[a-z0-9-]+)*\.(com|org|net|be|io|dev)\b`)
m := readManifest(t)
for i, r := range m.Resources {
if c, _ := r["content"].(string); host.MatchString(c) {
t.Errorf("resources[%d] (%v) carries content naming %q", i, r["id"], host.FindString(c))
}
}
raw, _ := os.ReadFile(filepath.Join(moduleDir, "module.json"))
if h := host.FindString(string(raw)); h != "" {
t.Errorf("module.json names %q", h)
}
}
// 1488: the toolchain's package.json and lock are shipped in the archive artifact `toolchain` and unpacked into the
// home's toolchain directory, where the program reads them.
func TestTheToolchainFilesAreAnArchiveUnpackedIntoTheHome(t *testing.T) {
m := readManifest(t)
r := resource(t, m, "toolchain-dir")
if r["type"] != "archive" || r["path"] != "${dir:state}/toolchain" || r["owner"] != "mesh-build-node" {
t.Errorf("toolchain-dir = %v, want an archive unpacked at ${dir:state}/toolchain", r)
}
if _, err := os.Stat(filepath.Join(moduleDir, "toolchain", "package-lock.json")); err != nil {
t.Errorf("the module carries no toolchain/package-lock.json: %v", err)
}
}
+10 -19
View File
@@ -21,7 +21,9 @@
"streams": [
{
"name": "toolchains-node",
"events": ["toolchain.**"]
"events": [
"toolchain.**"
]
}
],
"requires": [
@@ -117,28 +119,12 @@
},
{
"id": "toolchain-dir",
"type": "directory",
"type": "archive",
"path": "${dir:state}/toolchain",
"mode": "0700",
"owner": "mesh-build-node",
"artifact": "toolchain",
"unknown": "program"
},
{
"id": "toolchain-package",
"type": "file",
"path": "${dir:toolchain-dir}/package.json",
"owner": "mesh-build-node",
"mode": "0644",
"template": "files/toolchain/package.json"
},
{
"id": "toolchain-lock",
"type": "file",
"path": "${dir:toolchain-dir}/package-lock.json",
"owner": "mesh-build-node",
"mode": "0644",
"template": "files/toolchain/package-lock.json"
},
{
"id": "nodejs",
"type": "package",
@@ -177,6 +163,11 @@
"name": "files",
"kind": "archive",
"from": "files"
},
{
"name": "toolchain",
"kind": "archive",
"from": "toolchain"
}
]
}