Skip to content

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.