package builder import ( "context" "os" "path/filepath" "testing" ) const aBundle = `{"module":"greeter","version":"1", "build":{"artifacts":[ {"name":"code","kind":"bundle","language":"typescript","entrypoints":["index.js"]}]}, "resources":[ {"id":"files","type":"archive","path":"/opt/greeter","artifact":"code"}]}` // compiling is a runner that behaves like a toolchain: when asked to compile, it leaves output // where the toolchain says output lands. Without this the pack step has nothing to pack, and the // test would be asserting on a failure rather than on a build. type compiling struct{ *recorded } func (c compiling) run(ctx context.Context, dir, name string, args ...string) (string, error) { out, err := c.recorded.run(ctx, dir, name, args...) if name != "docker" || len(args) == 0 || args[0] != "run" { return out, err } // **Writes where it was TOLD to**, rather than to a fixed directory. A fake that always wrote // to one place would pass whether or not the builder gave each artifact its own — which is the // thing being tested. where := "" for i, a := range args { if a == "--outDir" && i+1 < len(args) { where = args[i+1] } } if where == "" { return out, err } made := filepath.Join(dir, where) if err := os.MkdirAll(made, 0o755); err != nil { return "", err } if err := os.WriteFile(filepath.Join(made, "index.js"), []byte("console.log(1)"), 0o644); err != nil { return "", err } return out, err } // **A module says what it is written in, and needs no Dockerfile.** This is the whole point of the // bundle recipe: the same module previously needed a hand-written recipe repeating an incantation // that is easy to get wrong in ways that fail somewhere else. // A bundle naming packages it keeps external is bundled with them as imports, and carries the // toolchain's node_modules for them — the one case it still does. // A language whose bundle carries its own dependencies copies nothing in: a Go binary is static. func TestOnlyALanguageWithARuntimeDirectoryCopiesDependenciesIn(t *testing.T) { ts, _ := ToolchainFor("typescript") if ts.Dependencies != "/app/runtime" { t.Fatalf("typescript bundles run with %q", ts.Dependencies) } for _, language := range []string{"go", "python"} { chain, _ := ToolchainFor(language) if chain.Dependencies != "" { t.Fatalf("%s copies %q into every bundle, and its bundles carry their own", language, chain.Dependencies) } } } // **Refused before anything is built, naming what to build first.** A base the mesh has not built // is not a compile that fails on its first line — it is a question somebody can answer, and saying // it early is the difference between a fixable message and one about a missing image. // A language the mesh does not build is refused the same way, and names what it can build. // **One module, two bundles, and neither packs the other.** // // The case that matters for real modules: a module is one piece of software and may still carry a // daemon in one language and tools in another (ADR 0040). An earlier version of this compiled // every bundle into the toolchain's single output directory, so two of them would overwrite each // other and then be packed together — one artifact containing both, twice.