diff --git a/.github/actions/build-project/action.yml b/.github/actions/build-project/action.yml
index 0d689b7..37f40e2 100644
--- a/.github/actions/build-project/action.yml
+++ b/.github/actions/build-project/action.yml
@@ -52,11 +52,6 @@ inputs:
runs:
using: composite
steps:
- - name: npm install
- shell: bash
- run: npm ci
- working-directory: src/tools
-
- name: Cache vendor deps
uses: actions/cache@v5
with:
diff --git a/BUILDING.md b/BUILDING.md
index 615a93d..2a0ebf0 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -82,7 +82,8 @@ The following tools are required to build Ant regardless of platform:
- **[Meson](https://mesonbuild.com/)** build system (and Ninja backend)
- **[CMake](https://cmake.org/)** (for the tlsuv subproject)
- **pkg-config**
-- **Node.js** >= 22 (used to generate the JS snapshot at build time)
+- **Node.js** >= 22 and **npm** (used to generate bundled JavaScript sources at
+ build time)
- **[Zig](https://ziglang.org/)** >= 0.15 (builds the package manager component)
- **Git**
@@ -182,6 +183,9 @@ meson setup build
meson compile -C build
```
+Meson runs `npm ci` in `src/tools` automatically before generating the bundled
+JavaScript headers; no separate npm install step is needed.
+
Alternatively, if you have [maid](https://github.com/exact-labs/maid) installed,
you can use the task runner:
diff --git a/meson/builtins/meson.build b/meson/builtins/meson.build
index 069af9f..5d7036f 100644
--- a/meson/builtins/meson.build
+++ b/meson/builtins/meson.build
@@ -9,6 +9,7 @@ builtin_bundle_h = custom_target(
'builtin_bundle',
input: builtin_files,
output: 'builtin_bundle_data.h',
+ depends: tools_npm_ci,
command: [
node,
src_root / 'src' / 'tools' / 'gen_builtin_bundle.js',
diff --git a/meson/meson.build b/meson/meson.build
index e80a6c6..ec1c411 100644
--- a/meson/meson.build
+++ b/meson/meson.build
@@ -37,5 +37,23 @@ endif
subdir('deps')
subdir('version')
+
+npm = find_program('npm', required: true)
+tools_npm_ci = custom_target(
+ 'tools_npm_ci',
+ input: [
+ src_root / 'src' / 'tools' / 'package.json',
+ src_root / 'src' / 'tools' / 'npm-shrinkwrap.json',
+ ],
+ output: 'tools_npm_ci.stamp',
+ command: [
+ python,
+ files('npm_ci.py'),
+ npm.full_path(),
+ src_root / 'src' / 'tools',
+ '@OUTPUT@',
+ ],
+)
+
subdir('snapshot')
subdir('builtins')
diff --git a/meson/npm_ci.py b/meson/npm_ci.py
new file mode 100644
index 0000000..59a815c
--- /dev/null
+++ b/meson/npm_ci.py
@@ -0,0 +1,18 @@
+import pathlib
+import subprocess
+import sys
+
+
+def main():
+ if len(sys.argv) != 4:
+ print('usage: npm_ci.py ', file=sys.stderr)
+ return 2
+
+ npm, tools_dir, stamp = sys.argv[1:]
+ subprocess.run([npm, 'ci'], cwd=tools_dir, check=True)
+ pathlib.Path(stamp).write_text('ok\n')
+ return 0
+
+
+if __name__ == '__main__':
+ raise SystemExit(main())
diff --git a/meson/snapshot/meson.build b/meson/snapshot/meson.build
index fff4186..fae91ba 100644
--- a/meson/snapshot/meson.build
+++ b/meson/snapshot/meson.build
@@ -9,6 +9,7 @@ snapshot_h = custom_target(
'snapshot',
input: core_files,
output: 'snapshot_data.h',
+ depends: tools_npm_ci,
command: [
node,
src_root / 'src' / 'tools' / 'gen_snapshot.js',