LPM CLI

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/cli

Pin 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_PATH

2. Authenticate reproducibly

For private packages on lpm.dev, two approaches:

permissions:
  id-token: write # required for OIDC

jobs:
  install:
    steps:
      - uses: actions/checkout@v4
      - run: npm install -g @lpm-registry/cli
      - run: lpm setup ci npmrc --oidc # writes .npmrc by exchanging OIDC token at install time
      - run: lpm ci --offline --strict-integrity

lpm setup ci npmrc --oidc exchanges the workflow's OIDC token for an LPM.dev Registry session token at runtime. Nothing committed, nothing in secret storage — the trust model is the workflow's identity.

Static token (the universal fallback)

- run: npm install -g @lpm-registry/cli
- run: lpm setup ci npmrc # writes .npmrc with ${LPM_TOKEN} placeholder
  env:
    LPM_TOKEN: ${{ secrets.LPM_TOKEN }}
- run: lpm ci --offline --strict-integrity
  env:
    LPM_TOKEN: ${{ secrets.LPM_TOKEN }}

Generate the token via lpm setup local locally with a short lifetime:

lpm setup local -d 30        # 30-day read-only token

Store the printed token as LPM_TOKEN in your CI's secret store.

For GitLab CI, mint LPM_OIDC_TOKEN via the id_tokens block with aud: https://lpm.devlpm setup ci gitlab emits a ready-to-paste snippet. The legacy LPM_GITLAB_OIDC_TOKEN env var is accepted as a back-compat alias. See Environment variables.

3. Generate the workflow with lpm setup ci

lpm setup ci github-actions       # prints a starter workflow snippet + auth command
lpm setup ci gitlab               # GitLab equivalent

Prints a starter snippet (OIDC permissions, lpm env pull, a deploy step) to stdout, followed by the lpm env oidc allow command you need to run once to authorize the repo. No files are written — copy the snippet into .github/workflows/<name>.yml (or .gitlab-ci.yml) yourself and adapt. --env=<mode> controls which env mode the snippet references (default production).

4. Reproducible installs

lpm ci --offline --strict-integrity --no-skills --no-editor-setup --no-security-summary
FlagWhat it does in CI
--offlineNo network. Replays entirely from lpm.lock + the global store. Errors if anything is missing.
--strict-integrityRefuse to install tarball-URL deps that don't declare an inline SRI. Disables trust-on-first-use for fresh installs.
--no-skillsSkip skills auto-install. Saves time.
--no-editor-setupCompatibility flag. Package skills do not create editor integrations.
--no-security-summarySkip 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.

5. 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.

Static token

- 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.

OIDC

On GitHub Actions, the same permissions: id-token: write setting used by lpm setup ci npmrc --oidc is enough for firewall checks:

permissions:
  id-token: write
  contents: read

jobs:
  install:
    steps:
      - uses: actions/checkout@v4
      - run: npm install -g @lpm-registry/cli
      - run: LPM_NPM_FIREWALL=enforce lpm ci --strict-integrity

When active LPM Firewall checks need auth and no LPM_TOKEN or stored LPM.dev Registry auth is already available, LPM CLI exchanges the CI OIDC token for a short-lived LPM.dev Registry token. If LPM_TOKEN or stored auth is available, there is no OIDC exchange.

On GitLab CI, mint LPM_OIDC_TOKEN with audience https://lpm.dev:

install:
  id_tokens:
    LPM_OIDC_TOKEN:
      aud: https://lpm.dev
  script:
    - npm install -g @lpm-registry/cli
    - LPM_NPM_FIREWALL=enforce lpm ci --strict-integrity

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-integrity

6. 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 --noEmit

Each 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-none

For compliance gates, use lpm licenses:

- run: lpm licenses --fail-on copyleft,missing
- run: lpm licenses --deny GPL-3.0 --deny AGPL-3.0

7. 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.

PathCache keyUse
~/.lpm/storelpm-store-${{ runner.os }}-${{ hashFiles('lpm.lock') }}Package store used by lpm ci --offline and lpm install --offline.
~/.lpm/pluginslpm-plugins-${{ runner.os }}-${{ hashFiles('lpm.json') }}Managed Oxlint, Biome, and Rolldown plugin binaries used by built-in tools.
~/.lpm/cache/dlxlpm-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 --apply

8. Publishing from CI

- run: lpm setup ci npmrc --oidc
- run: lpm publish --provenance --min-score 80 -y

--provenance requires OIDC and produces a Sigstore attestation. --min-score 80 blocks publishing if quality drops. -y skips the interactive prompt.

For npm-compatible publish provenance, target npm explicitly:

- run: lpm publish --npm --provenance --min-score 80 -y

Generated 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.

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

  • @latest in CI. Don't pin the binary to floating tags. Reproducible builds need a fixed toolchain.
  • lpm install without --offline in 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 online lpm fetch before the offline replay.
  • Forgetting permissions: id-token: write for OIDC in GitHub Actions.
  • Caching node_modules/ instead of ~/.lpm/store/. The LPM CLI store is the right cache key — node_modules is a layout, the store is content.
  • Skipping lpm audit to "save time". It's a few hundred ms; the time-not-paid is paid back on the next CVE.

See also