68 lines
6.0 KiB
Markdown
68 lines
6.0 KiB
Markdown
# mesh-build-node — the Node and TypeScript build module
|
|
|
|
Design 47, release R4c-3 (novox/hq ADR 0405, ADR 0466 §1, ADR 0469 §1 and §4). The module that builds and checks
|
|
TypeScript code on a build machine, as an account of its own (`runs-as`, no login shell, home at mode 0700), with the
|
|
packages it declares (`nodejs`, `npm`, `git`, `openssh`) and the capability `user-namespaces`. The bundle is Go
|
|
(`cmd/mesh-build-node`). It claims no seat: it is assigned to the build machines (ADR 0469 §6) and runs dark until the
|
|
builder uses it. Its forge key and ssh files are the same as mesh-build-go's (issue 1484).
|
|
|
|
## The recipe
|
|
|
|
`recipe.go` names the one Node version it builds with (`RecipeNode`, `v26.10.0`, as `node --version` prints it) and the
|
|
recipe's own version (`RecipeSteps`). The recipe version is a hash of those two and of the bytes of the lock file
|
|
`toolchain/package-lock.json`, so one changed byte of the lock, another Node or another recipe moves it.
|
|
|
|
## The toolchain directory
|
|
|
|
`toolchain/package.json` and its committed `package-lock.json` name typescript 5.9.3, esbuild 0.25.12, @types/node 22.20.1 (the types tsc checks node imports against),
|
|
@novox/mesh-sdk 0.1.14 and nats 2.29.3. The mesh unpacks both, as the archive artifact `toolchain`, into `<home>/toolchain/` (resource `toolchain-dir`; a
|
|
manifest names no host, and the lock does, so it is not a template); the program reads the lock from there. When `<cache dir>/toolchain/<recipe version>` is missing
|
|
the module runs `npm ci --ignore-scripts` there once (a directory without its `.installed` mark counts as missing).
|
|
|
|
**How the registry is reached** (the choice of issue 1488): the lock holds @novox/mesh-sdk at the forge's public
|
|
address, and npm 12 installs a locked tarball only from the registry it is configured with. The module therefore
|
|
requires `npm-package-registry` (read access, provided by gitea) and, like the lab module, takes its binding and sealed
|
|
secret as files in its home. The install gets a private npm config file with the `@novox` scope at the binding's
|
|
`public-root` (else scheme, address, port and npm path) and the bound account's credential, and removes it afterwards;
|
|
every other package resolves from the public registry. This is the way today's build agent names it.
|
|
|
|
The runtime tree for bundles with externals (the pruned sdk and nats today in `/app/runtime`) is not installed
|
|
separately: sdk and nats are in this directory's `node_modules`, and the build step (issue 1490) takes them from there.
|
|
|
|
## The guard and the health
|
|
|
|
At start, every ten minutes, before every step and at every health look, the module reads the lock again and the
|
|
installed Node (`node --version`) and writes it with the recipe version to `<node>.typescript` of its `toolchains`
|
|
state. A machine whose Node is not the recipe's refuses every step, the step ending `refused` on its key in `steps` with
|
|
both versions named, and `build_node_health` (the module's `runtime-health`) answers unhealthy with the same text, so
|
|
the controller raises the machine's condition. A step runs only after the toolchain directory is installed.
|
|
|
|
## Tools
|
|
|
|
`build_node_status` and `build_node_health` answer in their call. The step tools run behind the guard and the
|
|
toolchain install: a long step answers at once with its id and ends on `<node>.<id>` of `steps` (ADR 0469 §2).
|
|
|
|
- `build_node_fetch` {repository, commit, ref} (issue 1489): the commit over ssh as the module's account
|
|
(`buildstep.FetchCommit`, remote `ssh://mesh-forge/<owner>/<name>.git`) into `<work>/<id>/tree`, then for each
|
|
TypeScript module (a module.json with a typescript artifact) the packages its package.json names beyond the SDK,
|
|
`typescript` and `@types/*` (the toolchain carries those): none installs nothing; some need a package-lock.json, and
|
|
are installed as the build agent does (scratch copy without the SDK,
|
|
devDependencies and scripts; `npm ci --omit=dev --omit=peer --ignore-scripts --no-bin-links`; copies of the SDK
|
|
removed). The mesh's registry reaches `npm ci` through a private config file (`--userconfig`), so
|
|
other `@novox/` packages install. A module with packages and no lock does not fail the fetch: its result lists it
|
|
under `refused` with the reason, and a build of that bundle fails naming it (issue 1459). Every command runs through `forgekey.ExecRunnerClean(0)` with PATH, HOME, the locale and its own
|
|
words, nothing of this process's environment. Workspaces older than a day are removed at each fetch.
|
|
- `build_node_test` {workspace, module, level} (issue 1491): the unit level is the type-check (the toolchain's tsc, the
|
|
package's `tsconfig.json` or the build agent's flags over its .ts files) and the `test` script; the integration level
|
|
is the `test:integration` script, the type-check being its setup. Bounds (the Go module's; ADR 0445 names none for
|
|
TypeScript): unit 5 s, integration 30 s setup and 60 s running. The test script finds `tsc` and `esbuild` on its PATH through `<work>/<id>/bin` (shell scripts calling the toolchain's). A level over its bound says `over_bound` and passes;
|
|
a failed one finishes the step with `passed` false and the output (the step fails only when the tests could not be run). Neither script nor .ts files: no tests. The toolchain makes no bin
|
|
links, so a script runs `node` and files, not `tsc` by name.
|
|
- `build_node_build` {workspace, module, artifact} (issue 1490): the build agent's tsc and esbuild lines
|
|
(mesh-controller internal/builder, cited in `build.go`), the launchers, `package.json`, `buildstep.Pack`, and the
|
|
digest. No network. The build runs in a staging copy of the module at `<work>/<id>/stage-<artifact>/modules/module`, with the
|
|
toolchain's packages (not typescript or esbuild) copied to `stage-<artifact>/node_modules`: the build agent's depth
|
|
(`/app/modules/module`, `/app/node_modules`), so esbuild's `../../node_modules/...` path comments, and the bundle's
|
|
digest, equal its. Copies, not links, because the mesh makes no symlink. A bundle that keeps an external gets them in its own `node_modules`.
|
|
The packed archive is written beside the tree as `<artifact>.tar.gz`.
|