From 983a6c39c8c1dee1b636905ae67589dada51ac83 Mon Sep 17 00:00:00 2001 From: mesh-tools Date: Tue, 20 Oct 2026 21:15:56 +0000 Subject: [PATCH] Ship the Node build module's toolchain lock as an archive, not a template naming hosts (issue 1488) --- modules/mesh-build-node/README.md | 8 +-- .../mesh-build-node/build_integration_test.go | 2 +- .../cmd/mesh-build-node/manifest_test.go | 51 ++++++++++++++----- modules/mesh-build-node/module.json | 29 ++++------- .../{files => }/toolchain/package-lock.json | 0 .../{files => }/toolchain/package.json | 0 6 files changed, 52 insertions(+), 38 deletions(-) rename modules/mesh-build-node/{files => }/toolchain/package-lock.json (100%) rename modules/mesh-build-node/{files => }/toolchain/package.json (100%) diff --git a/modules/mesh-build-node/README.md b/modules/mesh-build-node/README.md index a2ec6a1f..19c16e8e 100644 --- a/modules/mesh-build-node/README.md +++ b/modules/mesh-build-node/README.md @@ -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 `/toolchain/` (resources `toolchain-package` -and `toolchain-lock`); the program reads the lock from there. When `/toolchain/` 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 `/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 `/toolchain/` 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 diff --git a/modules/mesh-build-node/cmd/mesh-build-node/build_integration_test.go b/modules/mesh-build-node/cmd/mesh-build-node/build_integration_test.go index 758de1bf..4b93208a 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/build_integration_test.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/build_integration_test.go @@ -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) } diff --git a/modules/mesh-build-node/cmd/mesh-build-node/manifest_test.go b/modules/mesh-build-node/cmd/mesh-build-node/manifest_test.go index 47ac2506..8fb06a2d 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/manifest_test.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/manifest_test.go @@ -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) + } +} diff --git a/modules/mesh-build-node/module.json b/modules/mesh-build-node/module.json index 09154b3f..812f7a0e 100644 --- a/modules/mesh-build-node/module.json +++ b/modules/mesh-build-node/module.json @@ -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" } ] } diff --git a/modules/mesh-build-node/files/toolchain/package-lock.json b/modules/mesh-build-node/toolchain/package-lock.json similarity index 100% rename from modules/mesh-build-node/files/toolchain/package-lock.json rename to modules/mesh-build-node/toolchain/package-lock.json diff --git a/modules/mesh-build-node/files/toolchain/package.json b/modules/mesh-build-node/toolchain/package.json similarity index 100% rename from modules/mesh-build-node/files/toolchain/package.json rename to modules/mesh-build-node/toolchain/package.json