The codebase and the CLI
The toolchain is JavaScript on Node, run untranspiled, behind a single CLI. This page covers the properties of that codebase you actually run into — because they shape how you install, run, and extend it.
One language, one surface
Salesforce projects already run Node for LWC and Jest, so the DevOps code lives on the same runtime:
one set of dependencies, one linter, one test runner. Everything is reached through the dia-scripts
CLI (see the architecture), and structured data is parsed into objects rather than
scraped — JSON with JSON.parse, XML through xml2js. The toolchain never regexes XML; it
parses it (deploy/xml-file.mjs). That is why a metadata
workaround reads and rewrites a document tree instead of matching
text.
It runs untranspiled — so the Node version matters
The scripts are executed as written, not compiled first. The upside is that what you read is what
runs; the consequence is that the runtime Node version is load-bearing. package.json pins an
engines floor, and scripts/dia-scripts/index.mjs checks it on startup and refuses to run on an
older Node. The floor tracks the CI image: CI runs on the Node the Salesforce CLI docker image ships,
so a script that works there works on any local Node at or above the floor. The concrete versions
and the install steps are in Set up the toolchain locally.
Why the install line looks the way it does
Every pipeline step that runs the toolchain installs with
npx -y npm@11.19.0 ci --prefer-offline --no-audit --ignore-scripts, and npm run setup insists
on the same npm floor locally. Two separate concerns meet in that one line.
The first is supply-chain posture. npm ci installs exactly the lockfile, and --ignore-scripts
blocks package install scripts — the usual vehicle for a compromised dependency to run code at
install time. The SF CLI plugins are exact-pinned devDependencies installed by that same npm ci
and only linked into sf afterwards (ci install-tools), so nothing is fetched from the registry
at pipeline time. The npx fetch of npm itself is the one deliberate exception: version-pinned and
integrity-verified.
The second is an npm bug. The npm bundled with the CI image's Node (the 10.9 line) carries a
dependency-resolution defect (npm/cli#8981, fixed in
11.10.1) that can fail npm ci with a bogus EBADPLATFORM error. Running the install through a
pinned newer npm sidesteps it. The wrapper becomes unnecessary once the pinned image advances to a
Node bundling npm 11.10.1 or newer; the same defect is why an older local npm can fail npm run
setup.
Commands are registered explicitly
Because it is a native ES-module project, the CLI cannot use yargs' directory auto-discovery
(commandDir does not work under ESM). Every command is registered with an explicit static
.command() import in cli/run-script.mjs. This is the one bit of boilerplate the module system
imposes, and it is why adding a command is two steps — write the
module, then register it — rather than one.
Heavy dependencies load lazily
@salesforce/core is expensive to import, so commands that need it (org pool-*, test-suite
generation) import it dynamically inside their handler rather than at module top level. Registering a
command therefore stays cheap: starting the CLI to run something unrelated does not pay for the
pooling library. The same pattern defers the pool-claim path in org scratch-create.