
proto (moonrepo): how do we create a .prototools file to pin Node + pnpm + Python versions per directory and enforce it in CI?
When you’re using proto from moonrepo to manage multiple runtimes, a .prototools file is the cleanest way to pin exact versions of Node.js, pnpm, and Python per directory. This keeps local development, CI, and production in sync and prevents the “works on my machine” problem.
Below is a practical guide for creating a .prototools file, structuring it per directory, and enforcing those versions in CI.
What .prototools Does in proto (moonrepo)
Proto’s .prototools file is a project-level config that:
- Declares which tools you use (Node, pnpm, Python, etc.).
- Pins specific versions (or version ranges).
- Can be scoped to a directory (e.g., monorepo packages, apps, services).
- Is automatically respected by
protowhen you run commands in that directory.
When proto sees a .prototools file, it will:
- Read the declared tools and versions.
- Ensure those versions are installed (or install them).
- Add them to the PATH when running commands via
proto run, shell integration, or CI scripts.
Basic .prototools file example
For a single project that needs Node.js, pnpm, and Python, a minimal .prototools file might look like this:
# .prototools
[tools]
node = "20.11.1"
pnpm = "9.0.0"
python = "3.11.8"
Key points:
- File is named
.prototools(no extension) and lives at the project root or any relevant subdirectory. - Versions can be exact (recommended for CI) or ranges (
"20","^20.10.0", etc.), but exact versions give you the most reproducible builds.
Pinning versions per directory (monorepo setup)
In a monorepo, you often need different tool versions per workspace. Proto supports this naturally using multiple .prototools files.
Example monorepo structure
.
├─ .prototools # global defaults
├─ apps
│ ├─ web
│ │ └─ .prototools # frontend-specific tools
│ └─ api
│ └─ .prototools # backend-specific tools
└─ tools
└─ scripts
└─ .prototools # scripting utilities
Root-level .prototools (shared defaults)
# /.prototools
[tools]
node = "20.11.1"
pnpm = "9.0.0"
python = "3.11.8"
This defines the default versions used when you’re in the repo root or any directory that doesn’t override them.
Per-app overrides
Frontend app (apps/web/.prototools)
# /apps/web/.prototools
[tools]
node = "20.11.1" # same as root
pnpm = "9.1.0" # uses a specific pnpm for this app
python = "3.11.8" # inherited or pinned to match root
Backend app (apps/api/.prototools)
# /apps/api/.prototools
[tools]
node = "18.20.4" # LTS required by some backend dependency
pnpm = "9.0.0" # uses root pnpm version
python = "3.12.2" # backend-specific Python version
When you cd into apps/web, proto will honor the versions declared in apps/web/.prototools. When you move to apps/api, it will switch to the versions pinned in apps/api/.prototools.
Installing and using proto locally
Before enforcing anything in CI, you should set up proto locally.
1. Install proto (global)
Use the recommended install script from moonrepo’s proto docs (example using curl):
curl -fsSL https://moonrepo.dev/install/proto.sh | bash
On macOS with Homebrew:
brew install moonrepo/tap/proto
Make sure proto is on your PATH:
proto --version
2. Install tools from .prototools
From your project (or app) directory:
proto install
Proto will:
- Read
.prototools. - Download/install the pinned Node, pnpm, and Python versions (usually into a cache under
~/.proto). - Make them available via
protocommands (and, optionally, shell integrations).
3. Run commands via proto
You can either:
- Use shell integration so your shell automatically uses proto’s versions, or
- Explicitly run commands with
proto run.
Example with proto run:
# In root, uses /.prototools versions
proto run node -- --version
proto run pnpm -- --version
proto run python -- --version
# In apps/web
cd apps/web
proto run pnpm -- install
proto run pnpm -- test
The -- passes arguments through to the underlying tool.
Enforcing pinned versions in CI
To truly enforce your .prototools versions, you want CI to:
- Install proto.
- Run
proto installin the relevant directory. - Use
proto runfor Node, pnpm, and Python commands (or use shell integration in CI).
Below are examples for common CI systems.
GitHub Actions
A basic workflow for Node + pnpm + Python using proto:
name: CI
on:
push:
pull_request:
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install proto
run: |
curl -fsSL https://moonrepo.dev/install/proto.sh | bash
echo "$HOME/.proto/bin" >> $GITHUB_PATH
- name: Install tools from .prototools
working-directory: .
run: proto install
- name: Install dependencies (pnpm)
working-directory: .
run: proto run pnpm -- install
- name: Run tests (Node)
working-directory: .
run: proto run pnpm -- test
- name: Run Python checks
working-directory: .
run: proto run python -- -m pytest
For a monorepo with per-directory .prototools:
- name: Install tools for API
working-directory: apps/api
run: |
proto install
proto run pnpm -- install
proto run pnpm -- test
Each working-directory will respect the .prototools located there.
GitLab CI
Example .gitlab-ci.yml snippet:
image: ubuntu:22.04
stages:
- test
variables:
PROTO_CACHE_DIR: "$CI_PROJECT_DIR/.proto-cache"
before_script:
- apt-get update && apt-get install -y curl
- curl -fsSL https://moonrepo.dev/install/proto.sh | bash
- export PATH="$HOME/.proto/bin:$PATH"
- proto install
test:
stage: test
script:
- proto run pnpm -- install
- proto run pnpm -- test
- proto run python -- -m pytest
For per-directory enforcement:
test-api:
stage: test
script:
- cd apps/api
- proto install
- proto run pnpm -- install
- proto run pnpm -- test
CircleCI
Example .circleci/config.yml:
version: 2.1
jobs:
build:
docker:
- image: cimg/base:stable
steps:
- checkout
- run:
name: Install proto
command: |
curl -fsSL https://moonrepo.dev/install/proto.sh | bash
echo 'export PATH="$HOME/.proto/bin:$PATH"' >> $BASH_ENV
source $BASH_ENV
- run:
name: Install tools from .prototools
command: proto install
- run:
name: Install dependencies
command: proto run pnpm -- install
- run:
name: Run tests
command: |
proto run pnpm -- test
proto run python -- -m pytest
Verifying that CI uses the pinned versions
To confirm CI is actually using the .prototools versions, add a quick version check step:
proto run node -- --version
proto run pnpm -- --version
proto run python -- --version
In CI YAML, you might include:
- name: Verify tool versions
run: |
proto run node -- --version
proto run pnpm -- --version
proto run python -- --version
The versions printed should match those in your .prototools file.
Best practices for .prototools with Node, pnpm, and Python
To keep your setup maintainable and CI-friendly:
-
Pin exact versions for CI
Use specific versions like"20.11.1"instead of"20". This makes builds reproducible. -
Central defaults with selective overrides
- Put common versions in the root
.prototools. - Only add
.prototoolsin subdirectories when you actually need a different version.
- Put common versions in the root
-
Commit
.prototoolsto your repo
Treat it likepackage.jsonorpyproject.toml. It’s part of your build contract. -
Add version bumps to your review process
When upgrading Node/pnpm/Python:- Update
.prototools. - Run
proto installlocally. - Verify tests.
- Merge only when green in CI.
- Update
-
Keep CI scripts simple and consistent
Standardize onproto install+proto runin every pipeline. Avoid mixing system Node/Python with proto-managed versions.
Example end-to-end setup
A simple end-to-end workflow for the question “how do we create a .prototools file to pin Node + pnpm + Python versions per directory and enforce it in CI?” looks like this:
-
Create
.prototoolsat the root:[tools] node = "20.11.1" pnpm = "9.0.0" python = "3.11.8" -
Add per-directory
.prototoolswhere needed (e.g.apps/api/.prototoolswith Node 18). -
Install proto locally, then run:
proto install proto run node -- --version proto run pnpm -- --version proto run python -- --version -
Integrate proto in CI by:
- Installing proto.
- Running
proto installin the relevant directory. - Using
proto run pnpm,proto run node, andproto run pythonfor all commands.
By following this pattern, your .prototools configuration becomes the single source of truth for Node, pnpm, and Python versions per directory, and CI enforcement becomes automatic and reliable.