Build TypeScript with the installed node and name no Node version (issue 1572, ADR 0477)

This commit is contained in:
2026-10-21 08:04:41 +00:00
parent fc4089cde9
commit edec7063b3
6 changed files with 61 additions and 53 deletions
@@ -53,23 +53,35 @@ func (m *Module) recipe() Recipe {
return m.Recipe
}
func (m *Module) guard(r Recipe) buildstep.Guard {
return buildstep.Guard{Language: "typescript", Want: r.Node, Recipe: r.Version(), Probe: m.Probe, Node: m.Node, Put: m.reportingPut()}
// guard is the SDK guard for the version the probe just read: the module names no Node version (ADR 0477 §2), so the
// version wanted is the one installed and the guard refuses only when node cannot be run, writing the installed
// version to <node>.typescript of the toolchains state every time.
func (m *Module) guard(r Recipe, installed string, probeErr error) buildstep.Guard {
return buildstep.Guard{Language: "typescript", Want: installed, Recipe: r.Version(), Node: m.Node, Put: m.reportingPut(),
Probe: func(context.Context) (string, error) { return installed, probeErr }}
}
// Check reads the installed toolchain, writes <node>.typescript of the toolchains state and answers nil, or the
// refusal (buildstep.Guard.Check). It runs at start, before every step, at every health look and every CheckRound.
// refusal when node cannot be run (buildstep.Guard.Check); another version than another node's is no refusal (ADR 0477
// §2). It runs at start, before every step, at every health look and every CheckRound.
func (m *Module) Check(ctx context.Context) error {
_, err := m.check(ctx)
return err
}
// check is Check that also answers the version it found installed.
func (m *Module) check(ctx context.Context) (string, error) {
if m.Reload != nil {
r, err := m.Reload()
if err != nil {
return &buildstep.Refusal{Text: err.Error(), Want: RecipeNode}
return "", &buildstep.Refusal{Text: err.Error()}
}
m.mu.Lock()
m.Recipe = r
m.mu.Unlock()
}
return m.guard(m.recipe()).Check(ctx)
installed, err := m.Probe(ctx)
return installed, m.guard(m.recipe(), installed, err).Check(ctx)
}
// Prepare is Check and then the toolchain directory of the recipe in force: what every step runs behind.
@@ -99,20 +111,20 @@ func (m *Module) Start(ctx context.Context) {
}
}
// Health is the module's health (the runtime-health tool, ADR 0366): unhealthy while the toolchain installed is not
// the recipe's, saying both versions, so the controller raises the node's condition. It looks again each time it is
// Health is the module's health (the runtime-health tool, ADR 0366): unhealthy only while node cannot be run (or the lock cannot
// be read), saying why, so the controller raises the node's condition; healthy it names the installed version. It looks again each time it is
// asked.
func (m *Module) Health(ctx context.Context) health.Answer {
if err := m.Check(ctx); err != nil {
installed, err := m.check(ctx)
if err != nil {
return health.Unhealthy(err.Error())
}
r := m.recipe()
return health.Healthy(fmt.Sprintf("node %s installed, as the recipe %s builds with", r.Node, r.Version()))
return health.Healthy(fmt.Sprintf("node %s installed, the version the module builds with (recipe %s)", installed, m.recipe().Version()))
}
// Long is a tool that runs a long step (ADR 0469 §2): it answers at once with the step's id, and the step ends on
// its key in the steps state. The guard runs inside the step, before run, so a node that refuses ends every step
// refused with both versions named.
// its key in the steps state. The guard runs inside the step, before run, so a node whose node cannot be run ends every
// step refused with the cause named.
func (m *Module) Long(tool string, run func(ctx context.Context, args map[string]any) (any, error)) func(map[string]any) (any, error) {
return func(args map[string]any) (any, error) {
id, err := buildstep.Start(m.Ctx, m.Steps, m.Node, tool, func(ctx context.Context) (any, error) {
@@ -145,7 +157,7 @@ func (m *Module) Status(ctx context.Context) map[string]any {
err := m.Check(ctx)
installed, _ := m.Probe(ctx)
r := m.recipe()
out := map[string]any{"node": m.Node, "recipe": r.Version(), "node-version": r.Node, "installed": installed, "refusing": err != nil}
out := map[string]any{"node": m.Node, "recipe": r.Version(), "installed": installed, "refusing": err != nil}
if m.Toolchain != nil {
out["toolchain-dir"] = m.Toolchain.Paths.Dir(r.Version())
out["toolchain-installed"] = m.Toolchain.Present(r.Version())