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
@@ -1,9 +1,9 @@
package main
// The recipe of the Node build module (novox/hq ADR 0405 §1 and §7, ADR 0469 §1 and §4; issue 1488): the one Node
// version it builds with, and the recipe's own version, a hash of the recipe's version, that Node version and the
// bytes of the lock file that names the TypeScript toolchain. A node whose installed Node differs from the recipe's
// refuses every step (module.go), so all builders run this version or none builds.
// The recipe of the Node build module (novox/hq ADR 0405 §7, ADR 0469 §4, ADR 0477 §1-§3; issues 1488, 1572): the
// recipe's own version, and the recipe version, a hash of that and the bytes of the lock file that names the
// TypeScript toolchain. The module names no Node version: it builds with the installed nodejs and npm, reports the
// version installed to the toolchains state, and refuses only when node cannot be run (module.go).
import (
"crypto/sha256"
@@ -12,20 +12,14 @@ import (
"os"
)
// RecipeNode is the Node version the recipe builds with: the text `node --version` prints for it, which is what the
// guard compares and what the indexer reads from <node>.typescript of the toolchains state.
const RecipeNode = "v26.10.0"
// RecipeSteps is the recipe's own version: changed whenever the steps of fetch, test or build change in a way that
// can change what they produce (ADR 0405 §7). It is one of the three values the recipe version hashes.
// can change what they produce (ADR 0405 §7). It is one of the two values the recipe version hashes.
const RecipeSteps = "1"
// Recipe is every value that makes up the recipe version.
type Recipe struct {
// Steps is RecipeSteps.
Steps string
// Node is the toolchain's version as `node --version` prints it.
Node string
// Lock is the hex sha256 of the bytes of toolchain/package-lock.json (LockHash).
Lock string
}
@@ -43,15 +37,16 @@ func ReadRecipe(lockPath string) (Recipe, error) {
if err != nil {
return Recipe{}, fmt.Errorf("the toolchain's lock file cannot be read: %w", err)
}
return Recipe{Steps: RecipeSteps, Node: RecipeNode, Lock: LockHash(raw)}, nil
return Recipe{Steps: RecipeSteps, Lock: LockHash(raw)}, nil
}
// Version is the recipe version: a hash of the recipe's own version, the Node version and the lock file's hash, so a
// change of any of the three moves it and with it every fingerprint that holds it (ADR 0405 §7, ADR 0465 §2). The
// Version is the recipe version: a hash of the recipe's own version and the lock file's hash, never a Node version
// (ADR 0477 §3): the toolchain enters each fingerprint once, as the version the build nodes agree on. A change of
// either moves it and with it every fingerprint that holds it (ADR 0405 §7, ADR 0465 §2). The
// fields are named in the hashed text, so no two recipes share a text.
func (r Recipe) Version() string {
h := sha256.New()
for _, f := range [][2]string{{"steps", r.Steps}, {"node", r.Node}, {"lock", r.Lock}} {
for _, f := range [][2]string{{"steps", r.Steps}, {"lock", r.Lock}} {
h.Write([]byte(f[0] + "=" + f[1] + "\n"))
}
return hex.EncodeToString(h.Sum(nil))[:16]