The bundle recipe: the one that both builds and packs. An archive packs a directory as it stands, so shipping compiled output meant compiling somewhere first — which meant a Dockerfile repeating the same incantation in every module. Two base arguments with no defaults, a working directory chosen so the SDK resolves upward, the compiler invoked by absolute path because the usual symlink is resolved away when the base image is assembled, a second stage, an environment variable naming the entrypoints. Most of the catalogue is unconverted and that is why; two conversions done in one session were each wrong twice with a working example open in the next window. A bundle says a language and a list of entrypoints. The mesh knows what the language implies. Anything a module could override there it would be writing a Dockerfile to override, so a toolchain is deliberately not configurable. Declared rather than inferred, both of them: guessing the language from which files are present makes a build depend on a directory listing, and guessing the entrypoints makes it change meaning when somebody adds a helper. A toolchain the mesh does not hold is refused before anything is compiled, naming what to build first — the same treatment a missing base already gets, because it is the same question and somebody can answer it. A language the mesh does not build is refused saying what would have worked, since the author is usually one word away. The list of languages is closed and adding to it is a decision. Every language is another implementation of the contracts every module shares, and those change rarely and cascade when they do (ADR 0039) — a mesh whose SDKs disagree about the envelope fails by ignoring messages rather than by failing to compile. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
57 lines
1.9 KiB
Go
57 lines
1.9 KiB
Go
package builder
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A language the mesh builds resolves to the toolchain that builds it.
|
|
func TestADeclaredLanguageSelectsItsToolchain(t *testing.T) {
|
|
chain, err := ToolchainFor("typescript")
|
|
if err != nil {
|
|
t.Fatalf("typescript is not buildable: %v", err)
|
|
}
|
|
if chain.Base == "" || chain.Artifact == "" {
|
|
t.Fatalf("a toolchain names no base to compile in: %+v", chain)
|
|
}
|
|
if len(chain.Compile) == 0 || chain.Output == "" {
|
|
t.Fatalf("a toolchain says nothing about how to compile or where output lands: %+v", chain)
|
|
}
|
|
}
|
|
|
|
// Case and stray whitespace are a module author's slip, not a different language.
|
|
func TestALanguageIsMatchedLoosely(t *testing.T) {
|
|
for _, said := range []string{"TypeScript", " typescript ", "TYPESCRIPT"} {
|
|
if _, err := ToolchainFor(said); err != nil {
|
|
t.Fatalf("%q was refused: %v", said, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// **A refusal says what would have worked.** A module author who names a language the mesh does
|
|
// not build is one word away from a language it does, and a bare "unsupported" makes them go
|
|
// looking for a list that exists in one place in the source.
|
|
func TestAnUnknownLanguageSaysWhatIsBuildable(t *testing.T) {
|
|
_, err := ToolchainFor("cobol")
|
|
if err == nil {
|
|
t.Fatal("a language nothing can build was accepted")
|
|
}
|
|
for _, want := range Languages() {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Fatalf("the refusal does not mention %q, which would have worked: %v", want, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// And saying nothing is its own message: the mesh chooses the compiler, so a bundle that names no
|
|
// language has not asked for anything in particular — which is a mistake rather than a default.
|
|
func TestABundleMustSayWhatItIsWrittenIn(t *testing.T) {
|
|
_, err := ToolchainFor("")
|
|
if err == nil {
|
|
t.Fatal("a bundle with no language was accepted, so the mesh guessed a compiler")
|
|
}
|
|
if !strings.Contains(err.Error(), "must say") {
|
|
t.Fatalf("the refusal does not say a language is required: %v", err)
|
|
}
|
|
}
|