From 2399a4f4cc8e4761bc97f170ab0d8860bba9cdea Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 22 Oct 2026 17:59:43 +0200 Subject: [PATCH] =?UTF-8?q?Build=20a=20TypeScript=20package=20with=20the?= =?UTF-8?q?=20Node=20build=20module,=20published=20only=20for=20a=20trunk?= =?UTF-8?q?=20fingerprint;=20a=20version=20taken=20fails=20the=20key=20unt?= =?UTF-8?q?il=20the=20fingerprint=20changes=20(issue=201587,=20B5,=20ADR?= =?UTF-8?q?=200498=20=C2=A75,=20ADR=200500=20=C2=A74)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/build/plan.go | 2 + .../internal/build/r4e_test.go | 45 +++++++++++++++++++ .../internal/build/run_test.go | 9 +++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/modules/mesh-delivery-builder/internal/build/plan.go b/modules/mesh-delivery-builder/internal/build/plan.go index 33d48e6..8c83b36 100644 --- a/modules/mesh-delivery-builder/internal/build/plan.go +++ b/modules/mesh-delivery-builder/internal/build/plan.go @@ -104,6 +104,8 @@ func PlanOf(m catalogue.Manifest) (Plan, error) { return Plan{}, &NotPinned{Artifact: a.Name, From: a.From} case a.Kind == catalogue.ArtifactUpstream: p.Tasks = append(p.Tasks, Task{Artifact: a.Name, Kind: a.Kind, Module: ImageBuildModule, From: a.From}) + case a.Kind == catalogue.ArtifactPackage && a.Language == "typescript": + p.Tasks = append(p.Tasks, Task{Artifact: a.Name, Kind: a.Kind, Module: buildrun.NodeBuildModule, Language: "typescript"}) case a.Kind == catalogue.ArtifactBundle: return Plan{}, &Refused{Artifact: a.Name, What: fmt.Sprintf("bundle of language %q", a.Language)} case a.Kind == catalogue.ArtifactPackage: diff --git a/modules/mesh-delivery-builder/internal/build/r4e_test.go b/modules/mesh-delivery-builder/internal/build/r4e_test.go index 03fa95d..a4b7379 100644 --- a/modules/mesh-delivery-builder/internal/build/r4e_test.go +++ b/modules/mesh-delivery-builder/internal/build/r4e_test.go @@ -155,3 +155,48 @@ func TestACopyThatFailsNotStoredFailsTheKeyAndIsBuiltAgain(t *testing.T) { // B4 t.Errorf("build = %+v, want done on the new revision", v) } } + +var tsPackage = catalogue.Artifact{Name: "lib", Kind: "package", Language: "typescript"} + +func TestATypeScriptPackageIsBuiltByTheNodeModuleAndPublishedOnlyForTheTrunk(t *testing.T) { // B5, ADR 0498 clause 5, ADR 0500 clause 4 + c := newRig(t, tsPackage) + c.bm.packages["lib"] = "@novox/mesh-sdk@0.1.15" + trunkModule(t, c) + c.b.Candidate(context.Background(), "feat%2Fx", candidateValue(t, "feat/x", 7, headX)) + c.b.Wait() + v := valueOf(t, c.st, c.key) + if v.State != delivery.BuildDone || len(v.Artifacts) != 1 || v.Artifacts[0].Kind != "package" || v.Artifacts[0].Reference != "" { + t.Fatalf("candidate build = %+v, want done with the packed digest and no reference", v) + } + for _, call := range c.bm.callsOf("build_node_build") { + if _, ok := call.body.(map[string]any)["publish"]; ok { + t.Errorf("a candidate's package build was asked to publish: %+v", call.body) + } + } + + r := newRig(t, tsPackage) + r.bm.packages["lib"] = "@novox/mesh-sdk@0.1.15" + v = r.consider(t, "modules/m") + if v.State != delivery.BuildDone || v.Artifacts[0].Reference != "@novox/mesh-sdk@0.1.15" { + t.Fatalf("trunk build = %+v, want done naming the published version", v) + } + if n := len(r.bm.callsOf("build_node_build")); n == 0 { + t.Error("the package was not built by the Node build module") + } +} + +func TestAVersionTakenFailsTheKeyUntilTheFingerprintChanges(t *testing.T) { // B5, ADR 0498 clause 5 + r := newRig(t, tsPackage) + r.bm.packages["lib"] = "@novox/mesh-sdk@0.1.15" + r.bm.fails["ace/build_node_build"] = delivery.VersionTaken + "@novox/mesh-sdk@0.1.15 is published with another packed file" + r.bm.fails["novox/build_node_build"] = r.bm.fails["ace/build_node_build"] + v := r.consider(t, "modules/m") + if v.State != delivery.BuildFailed || !strings.HasPrefix(v.Error, delivery.VersionTaken) || !strings.Contains(v.Error, "0.1.15") { + t.Fatalf("build = %+v, want failed opening %q naming the version", v, delivery.VersionTaken) + } + writes := r.st.writes + r.consider(t, "modules/m") + if r.st.writes != writes { + t.Errorf("a version-taken key was built again (%d writes, was %d)", r.st.writes, writes) + } +} diff --git a/modules/mesh-delivery-builder/internal/build/run_test.go b/modules/mesh-delivery-builder/internal/build/run_test.go index 739bdb3..f5d6068 100644 --- a/modules/mesh-delivery-builder/internal/build/run_test.go +++ b/modules/mesh-delivery-builder/internal/build/run_test.go @@ -25,6 +25,8 @@ type buildModules struct { fails map[string]string // `/` -> the reason the step fails with // noRef names `/` whose bundle or archive build answers no reference (its push not answered). noRef map[string]bool + // packages are the package artifacts by name, each with the `@` its trunk build publishes (ADR 0498 clause 5). + packages map[string]string // hold, when set before the builds start, blocks every call until it is closed; entered is sent to as a call arrives. // conflicts, when set, is a file a fetch with a base reports in conflict; noMerge makes it answer neither a merge nor a conflict. conflicts string @@ -34,7 +36,7 @@ type buildModules struct { } func newBuildModules() *buildModules { - return &buildModules{steps: &fakeSteps{steps: map[string]delivery.Step{}}, digests: map[string]string{}, fails: map[string]string{}, noRef: map[string]bool{}} + return &buildModules{steps: &fakeSteps{steps: map[string]delivery.Step{}}, digests: map[string]string{}, fails: map[string]string{}, noRef: map[string]bool{}, packages: map[string]string{}} } func (b *buildModules) CallOn(node, key string, body any) (json.RawMessage, error) { @@ -71,6 +73,11 @@ func (b *buildModules) CallOn(node, key string, body any) (json.RawMessage, erro } if tool == "build_image_copy" { // mesh-build-image's copy answers the digest and where it copied to (ADR 0498 clause 4) step.Result = json.RawMessage(`{"artifact":"` + fmt.Sprint(args["artifact"]) + `","digest":"` + d + `","reference":"registry.example/` + fmt.Sprint(args["module"]) + `/` + fmt.Sprint(args["artifact"]) + `@` + d + `","pushed":true}`) + } else if pkg, ok := b.packages[fmt.Sprint(args["artifact"])]; ok { // a package names its version only when published + step.Result = json.RawMessage(`{"digest":"` + d + `"}`) + if args["publish"] == true { + step.Result = json.RawMessage(`{"digest":"` + d + `","reference":"` + pkg + `"}`) + } } else if tool == "build_image_build" && args["publish"] == true { step.Result = json.RawMessage(`{"digest":"` + d + `","reference":"registry.example/m/` + fmt.Sprint(args["artifact"]) + `@` + d + `"}`) } else if tool != "build_image_build" && !b.noRef[node+"/"+fmt.Sprint(args["artifact"])] {