mesh-build-node: emit a package that builds before its tests, so tests importing its own output find it (issue 1869, part of issue 1309's run step)

A package with a build or prepare script and a tsconfig.json is built with tsc -p . in the
shut-off run step, at both levels, instead of the --noEmit type-check; the fetch's
--ignore-scripts never runs those scripts. mesh-sdk's tests import ../dist and failed
ERR_MODULE_NOT_FOUND. The result names the step build.
This commit is contained in:
2026-10-22 18:23:25 +02:00
parent b6247f837d
commit 22992da15d
3 changed files with 99 additions and 3 deletions
@@ -89,3 +89,28 @@ func TestTheFixturePassesTheUnitLevel(t *testing.T) {
t.Errorf("result = %+v", r)
}
}
// Issue 1869: a package whose test imports its own build output (as mesh-sdk's do) passes the unit level: the test step
// emits it with the real tsc, shut off, before the test script runs.
func TestAPackageWhoseTestsImportItsBuildPassesTheUnitLevel(t *testing.T) {
if _, err := exec.LookPath("bwrap"); err != nil {
t.Skip("NOT RUN: no bubblewrap installed where this check runs; every build node has it (the module declares it)")
}
m, work := realMachine(t)
for p, body := range map[string]string{
"modules/p/package.json": `{"name":"p","type":"module","scripts":{"build":"tsc","test":"node --test test/a.test.mjs","prepare":"tsc"}}`,
"modules/p/tsconfig.json": `{"compilerOptions":{"module":"NodeNext","moduleResolution":"NodeNext","target":"ES2022","outDir":"dist","rootDir":"src"},"include":["src"]}`,
"modules/p/src/index.ts": "export const answer: number = 42;\n",
"modules/p/test/a.test.mjs": "import { test } from 'node:test';\nimport assert from 'node:assert';\nimport { answer } from '../dist/index.js';\n" +
"test('the build is there', () => assert.equal(answer, 42));\n",
} {
write(t, filepath.Join(work, "w1", "tree", filepath.FromSlash(p)), body, 0o644)
}
out, err := m.test(context.Background(), map[string]any{"workspace": "w1", "module": "modules/p", "level": "unit"})
if err != nil {
t.Fatal(err)
}
if r := out.(*TestResult); !r.Passed || !slices.Equal(r.Ran, []string{"build", "test"}) {
t.Errorf("result = %+v", r)
}
}
@@ -138,13 +138,20 @@ func (m *Module) test(ctx context.Context, args map[string]any) (any, error) {
return nil
}
if len(sources) > 0 {
// A package that builds (a build or prepare script, which the fetch's --ignore-scripts never runs) is emitted
// by tsc into the workspace, shut off like every run step, so its tests find what they import of its own output
// (issue 1869: mesh-sdk's tests import ../dist); any other is type-checked without emitting.
name, line := "type-check", m.typeCheck(dir, sources)
if emit := m.emitBuild(dir, scripts); emit != nil {
name, line = "build", emit
}
start := m.now()
r := m.runIn(ctx, root, false, dir, nil, "node", m.typeCheck(dir, sources)...)
r := m.runIn(ctx, root, false, dir, nil, "node", line...)
check = m.now().Sub(start)
if err := ran("type-check", r); err != nil {
if err := ran(name, r); err != nil {
return nil, err
}
res.Ran = append(res.Ran, "type-check")
res.Ran = append(res.Ran, name)
}
if hasScript && len(failed) == 0 {
start := m.now()
@@ -220,6 +227,18 @@ func (m *Module) typeCheck(dir string, sources []string) []string {
"--target", "ES2022", "--rootDir", "."}, sources...)
}
// emitBuild is tsc's line that builds a package as its tsconfig.json says (`tsc -p .`, emitting where it says), when the
// package builds (a build or prepare script) and has a tsconfig.json; nil otherwise.
func (m *Module) emitBuild(dir string, scripts map[string]string) []string {
if scripts["build"] == "" && scripts["prepare"] == "" {
return nil
}
if _, err := os.Stat(filepath.Join(dir, "tsconfig.json")); err != nil {
return nil
}
return []string{m.tsc(), "-p", "."}
}
// packageScripts are the scripts of the package.json in dir (none without one).
func packageScripts(dir string) (map[string]string, error) {
raw, err := os.ReadFile(filepath.Join(dir, "package.json"))
@@ -0,0 +1,52 @@
package main
import (
"context"
"slices"
"strings"
"testing"
)
// The package.json of mesh-sdk's TypeScript package (feat/r4e-sdk 9cb4d59), whose tests import its own build output
// (test/retire.test.ts imports ../dist/...): built by `tsc` (build and prepare), which a fetch with --ignore-scripts
// never runs.
const sdkPackageJSON = `{"name":"@novox/mesh-sdk","version":"0.1.15","type":"module","exports":{".":"./dist/index.js"},"files":["dist"],` +
`"scripts":{"build":"tsc","test":"node --test --experimental-strip-types 'test/*.test.ts'","prepare":"tsc"}}`
// Issue 1869: a package that builds (a build or prepare script) is emitted by tsc into the workspace, inside the shut-off
// run step, at both levels, before its tests, which import what it emits; the result names it "build". A package that
// does not build is still type-checked without emitting.
func TestAPackageThatBuildsIsEmittedBeforeItsTests(t *testing.T) {
for _, level := range []string{"unit", "integration"} {
m, f, work := newMachine(t)
pkg := sdkPackageJSON
if level == "integration" {
pkg = strings.Replace(pkg, `"prepare":"tsc"`, `"prepare":"tsc","test:integration":"node --test test/it.mjs"`, 1)
}
workspaceWith(t, work, map[string]string{"package.json": pkg, "tsconfig.json": `{"compilerOptions":{"outDir":"dist"}}`,
"src/index.ts": "export const a = 1;\n", "test/retire.test.ts": "import '../dist/index.js';\n"})
out, err := m.test(context.Background(), testArgs(level))
if err != nil {
t.Fatal(err)
}
r := out.(*TestResult)
if !slices.Contains(r.Ran, "build") || slices.Contains(r.Ran, "type-check") {
t.Errorf("%s: ran %v, want the build and no separate type-check", level, r.Ran)
}
first := joined(f.calls[0])
if strings.Contains(first, "--noEmit") || !strings.HasSuffix(first, "typescript/bin/tsc -p .") {
t.Errorf("%s: the build ran %s, want tsc -p . emitting", level, first)
}
if len(f.shut) == 0 || !strings.Contains(f.shut[0].command, "tsc -p .") {
t.Errorf("%s: the build did not run shut off", level)
}
}
m, f, work := newMachine(t)
workspaceWith(t, work, map[string]string{"package.json": `{"scripts":{"test":"node --test"}}`, "tsconfig.json": `{}`, "index.ts": "export const a = 1;\n"})
if _, err := m.test(context.Background(), testArgs("unit")); err != nil {
t.Fatal(err)
}
if first := joined(f.calls[0]); !strings.Contains(first, "--noEmit") {
t.Errorf("a package that does not build ran %s, want the type-check", first)
}
}