Add the Node build module's fetch, test step, registry and toolchain install (issues 1488, 1489, 1491), part 17 of 19
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
package main
|
||||
|
||||
// The toolchain directory (novox/hq ADR 0405 §5, ADR 0469 §4; issue 1488): typescript, esbuild, @novox/mesh-sdk and
|
||||
// nats by the versions the module's own lock names, installed by `npm ci --ignore-scripts` into
|
||||
// <cache dir>/toolchain/<recipe version> when that directory is missing. No bin links are made (--no-bin-links): the
|
||||
// toolchain holds no symlink, and tsc and esbuild are run by their real paths. A change of the lock, of the Node
|
||||
// version or of the recipe moves the recipe version and so installs a fresh directory beside the old one; steps use the one named
|
||||
// by the current recipe.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
// installedMark is written last into a toolchain directory, so a directory an install did not finish is not taken
|
||||
// for a toolchain.
|
||||
const installedMark = ".installed"
|
||||
|
||||
// Paths are the module's places on its machine.
|
||||
type Paths struct {
|
||||
// Home is the account's home (the state directory): where the mesh writes toolchain/package.json and
|
||||
// toolchain/package-lock.json (module.json resources) and the registry binding and secret.
|
||||
Home string
|
||||
// Cache is the cache directory, outside the home (ADR 0466 §1).
|
||||
Cache string
|
||||
}
|
||||
|
||||
// LockPath is the committed lock file the mesh writes into the home.
|
||||
func (p Paths) LockPath() string { return filepath.Join(p.Home, "toolchain", "package-lock.json") }
|
||||
|
||||
// Dir is the toolchain directory of a recipe version.
|
||||
func (p Paths) Dir(version string) string { return filepath.Join(p.Cache, "toolchain", version) }
|
||||
|
||||
// Toolchain installs the toolchain directories.
|
||||
type Toolchain struct {
|
||||
Paths Paths
|
||||
Run forgekey.Runner
|
||||
Registry func() (Registry, error)
|
||||
}
|
||||
|
||||
// Present reports whether the recipe version's toolchain directory is installed.
|
||||
func (t Toolchain) Present(version string) bool {
|
||||
_, err := os.Stat(filepath.Join(t.Paths.Dir(version), installedMark))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Ensure installs the recipe version's toolchain directory unless it is there: the module's package.json and lock
|
||||
// are copied in, and `npm ci --ignore-scripts` runs once, the mesh's registry named for the @novox scope. A directory
|
||||
// an earlier install left unfinished is removed first.
|
||||
func (t Toolchain) Ensure(ctx context.Context, version string) error {
|
||||
if t.Present(version) {
|
||||
return nil
|
||||
}
|
||||
dir := t.Paths.Dir(version)
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range []string{"package.json", "package-lock.json"} {
|
||||
raw, err := os.ReadFile(filepath.Join(t.Paths.Home, "toolchain", name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("the toolchain's %s cannot be read: %w", name, err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, name), raw, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
reg, err := t.Registry()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
npmrc := filepath.Join(dir, ".npmrc-install")
|
||||
if err := os.WriteFile(npmrc, []byte(reg.Npmrc()), 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(npmrc)
|
||||
ran := t.Run(ctx, []string{"PATH=" + pathOrDefault(), "HOME=" + t.Paths.Home, "LC_ALL=C", "npm_config_cache=" + filepath.Join(t.Paths.Cache, "npm")},
|
||||
"npm", "ci", "--ignore-scripts", "--no-bin-links", "--no-audit", "--no-fund", "--prefix", dir, "--userconfig", npmrc)
|
||||
if ran.Err != "" || ran.Status != 0 {
|
||||
return fmt.Errorf("npm ci --ignore-scripts for the toolchain %s failed (status %d %s): %s",
|
||||
version, ran.Status, ran.Err, tail(ran.Stderr))
|
||||
}
|
||||
return os.WriteFile(filepath.Join(dir, installedMark), []byte(version+"\n"), 0o600)
|
||||
}
|
||||
|
||||
func tail(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) > 400 {
|
||||
s = s[len(s)-400:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (t *Toolchain) homeOrEmpty() string {
|
||||
if t == nil {
|
||||
return ""
|
||||
}
|
||||
return t.Paths.Home
|
||||
}
|
||||
Reference in New Issue
Block a user