CI/CD setup
Configure LPM CLI for GitHub Actions, GitLab CI, and other CI systems.
This guide covers the moving parts of running LPM CLI in CI: pinning the binary, authenticating reproducibly, gating on quality and security signals, and producing reliable artifacts. We'll go through GitHub Actions in detail; GitLab CI follows the same shape.
1. Install LPM CLI in CI
The fastest path is npm — it works everywhere:
- run: npm install -g @lpm-registry/cliPin the version explicitly. Don't @latest in CI — reproducible builds depend on a fixed toolchain.
For Linux runners that don't have npm bootstrapped, use the standalone installer:
- run: curl -fsSL https://cli.lpm.dev/install | sh
- run: echo "$HOME/.lpm/bin" >> $GITHUB_PATH2. Authenticate reproducibly
For private packages on lpm.dev, use a read-scoped Registry token.
Static token
- run: npm install -g @lpm-registry/cli
- run: lpm fetch
env:
LPM_TOKEN: ${{ secrets.LPM_TOKEN }}
- run: lpm ci --offline --strict-integrityCreate a read-scoped token and store it as LPM_TOKEN in your CI's secret store. LPM CLI reads it directly and routes @lpm.dev/* packages without an .npmrc; use lpm setup ci npmrc only when another npm-compatible client in the job needs Registry authentication. That command must resolve a bearer first and writes the literal only to an atomically replaced, owner-only project file; its JSON output is redacted.
3. Reproducible installs
lpm ci --offline --strict-integrity --no-skills --no-editor-setup --no-security-summary| Flag | What it does in CI |
|---|---|
--offline | No network. Replays entirely from lpm.lock + the global store. Errors if anything is missing. |
--strict-integrity | Refuse to install tarball-URL deps that don't declare an inline SRI. Disables trust-on-first-use for fresh installs. |
--no-skills | Skip skills auto-install. Saves time. |
--no-editor-setup | Compatibility flag. Package skills do not create editor integrations. |
--no-security-summary | Skip the post-install security report. Pair with lpm audit as a separate explicit step. |
If lpm ci fails, the lockfile is missing, stale, or cannot be replayed from the available store. Run lpm install locally, commit the updated lpm.lock, and commit lpm.lockb only when LPM CLI writes it.
4. npm firewall in CI
The npm firewall is a network verdict check, so the command that runs it cannot be --offline. Use LPM_NPM_FIREWALL=monitor or LPM_NPM_FIREWALL=enforce for a per-job policy, or run lpm config firewall --set enforce on runners where the home directory is intentionally persistent.
- run: npm install -g @lpm-registry/cli
- run: LPM_NPM_FIREWALL=enforce lpm ci --strict-integrity
env:
LPM_TOKEN: ${{ secrets.LPM_TOKEN }}LPM_TOKEN is used for the firewall verdict API and any lpm.dev package auth in the same run.
If the workflow deliberately separates an online store warm from an offline replay, put the firewall on the online step and keep the replay offline:
- run: LPM_NPM_FIREWALL=enforce lpm fetch
- run: lpm ci --offline --strict-integrity5. Gating
The standard gate sequence:
- run: lpm ci --offline --strict-integrity
- run: lpm trust diff --assert-none
- run: lpm audit --fail-on vuln # fail on vulnerabilities (not behavior signals)
- run: lpm licenses --fail-on copyleft --deny GPL-3.0 # optional license policy gate
- run: lpm test # forwards vitest/jest exit code
- run: lpm lint # forwards oxlint exit code
- run: lpm fmt --check # fails if anything is unformatted
- run: lpm check # tsc --noEmitEach step exits non-zero on failure — no special handling needed. See Exit codes. lpm trust diff is informational by default; add --assert-none to gate on trustedDependencies drift. Use lpm trust diff --assert-none --json if you also want the structured diff envelope in CI logs.
For finer-grained security gates, use lpm query:
- run: lpm query ":vulnerable:not(:built)" --assert-none
- run: lpm query ":eval:scripts" --assert-noneFor compliance gates, use lpm licenses:
- run: lpm licenses --fail-on copyleft,missing
- run: lpm licenses --deny GPL-3.0 --deny AGPL-3.06. Caching ~/.lpm/
Cache the content LPM CLI can safely reuse. Do not cache node_modules/; it is a generated layout that LPM CLI recreates from the lockfile and store.
| Path | Cache key | Use |
|---|---|---|
~/.lpm/store | lpm-store-${{ runner.os }}-${{ hashFiles('lpm.lock') }} | Package store used by lpm ci --offline and lpm install --offline. |
~/.lpm/plugins | lpm-plugins-${{ runner.os }}-${{ hashFiles('lpm.json') }} | Managed Oxlint, Biome, and Rolldown plugin binaries used by built-in tools. |
~/.lpm/cache/dlx | lpm-dlx-${{ runner.os }}-${{ hashFiles('lpm.lock') }} | Optional. Speeds up repeated lpm dlx / lpx calls; entries still expire after their normal TTL. |
The store is the important cache:
- name: Cache LPM CLI store
uses: actions/cache@v4
with:
path: ~/.lpm/store
key: lpm-store-${{ runner.os }}-${{ hashFiles('lpm.lock') }}
restore-keys: lpm-store-${{ runner.os }}-If your workflow runs managed tools, cache plugins separately so tool downloads do not share the dependency-store key:
- name: Cache LPM CLI plugins
uses: actions/cache@v4
with:
path: ~/.lpm/plugins
key: lpm-plugins-${{ runner.os }}-${{ hashFiles('lpm.json') }}
restore-keys: lpm-plugins-${{ runner.os }}-lpm install --offline is fast in benchmarks when the store + lockfile match. On the VitePress docs fixture, up-to-date checks land around 14 ms and warm reinstalls around 387 ms. Caching ~/.lpm/store turns a cold CI install into a warm one.
The cache key is the lockfile hash. Typical CI runners are short-lived enough that orphan cleanup isn't needed; if you do persist the store across runs and want to bound its size, run lpm cache prune --apply periodically.
For persistent self-hosted runners, also clean short-lived caches on your own schedule:
lpm cache clean dlx
lpm cache prune --apply7. Publishing from CI
- run: lpm publish --provenance --min-score 60 --wait -y--provenance requires OIDC and produces a Sigstore attestation. --min-score 60 blocks a low local quality score.
--wait keeps the job active until the LPM.dev Registry version becomes active under its distribution rules. The default wait limit is 20 minutes.
Some quality points remain pending until Registry analysis. Set the threshold from a representative lpm publish --check result. -y skips the prompt.
For npm-compatible publish provenance, target npm explicitly:
- run: lpm publish --npm --provenance -yGenerated npm provenance requires public access. LPM CLI's npm default is public; if your config sets restricted access, change it to public or skip generated provenance. A normal npm-only publish does not run the LPM.dev quality scorer, so --min-score is not a quality gate for this command; keep the project's lint, test, type-check, and audit jobs explicit in CI.
For multi-registry publishing in one job, configure lpm.json > publish.registries and pass nothing — lpm publish walks every configured target. Custom npm-compatible registries need an exact registry-scoped token from lpm login --login-registry <URL> --token <T>; NPM_TOKEN only authenticates https://registry.npmjs.org.
Common pitfalls
@latestin CI. Don't pin the binary to floating tags. Reproducible builds need a fixed toolchain.lpm installwithout--offlinein CI. Lets lockfile drift slip through unnoticed.- Combining active npm firewall with
--offline. Firewall verdicts need network access. Run the firewall-protected install online, or firewall-gate an onlinelpm fetchbefore the offline replay. - Forgetting
permissions: id-token: writewhen publishing with OIDC or generated provenance in GitHub Actions. - Caching
node_modules/instead of~/.lpm/store/. The LPM CLI store is the right cache key —node_modulesis a layout, the store is content. - Skipping
lpm auditto "save time". It's a few hundred ms; the time-not-paid is paid back on the next CVE.
See also
lpm setup ci npmrc— generate.npmrcfor npm-compatible CI clientslpm ci— frozen lockfile installlpm env print --ci— CI-native env outputlpm audit— security gating- Exit codes — what each non-zero code means
- Environment variables —
LPM_TOKEN,LPM_OIDC_TOKEN