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:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user