LPM-cli

CI/CD setup

Configure LPM for GitHub Actions, GitLab CI, and other CI systems.

This guide covers the moving parts of running LPM 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 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://lpm.dev/install.sh | 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 --oidc # writes .npmrc by exchanging OIDC token at install time
      - run: lpm install --offline --strict-integrity

lpm setup ci --oidc exchanges the workflow's OIDC token for an LPM 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 # writes .npmrc with ${LPM_TOKEN} placeholder
  env:
    LPM_TOKEN: ${{ secrets.LPM_TOKEN }}
- run: lpm install --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 ci setup 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 ci setup

lpm ci setup github-actions       # prints a starter workflow snippet + auth command
lpm ci setup 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 install --offline --strict-integrity --no-skills --no-editor-setup --no-security-summary
FlagWhat it does in CI
--offlineNo network. Resolves entirely from lpm.lock + the global store. Errors if anything is missing — turns "lockfile drift went unnoticed" into a hard fail.
--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-setupSkip editor auto-integration. CI has no editor.
--no-security-summarySkip the post-install security report. Pair with lpm audit as a separate explicit step.

If --offline fails, the lockfile is out of date. Run lpm install locally without --offline, commit the updated lockfile.

5. Gating

The standard gate sequence:

- run: lpm install --offline --strict-integrity
- run: lpm trust diff --assert-none
- run: lpm audit --fail-on vuln # fail on vulnerabilities (not behavior signals)
- 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 --json trust diff --assert-none 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

6. Caching ~/.lpm/

To avoid re-downloading the global store on every CI run:

- name: Cache LPM store
  uses: actions/cache@v4
  with:
    path: ~/.lpm/store
    key: lpm-store-${{ hashFiles('lpm.lock') }}
    restore-keys: lpm-store-

lpm install --offline is fast (~6–23 ms in benchmarks) when the store + lockfile match. Caching the 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.

7. Publishing from CI

- run: lpm setup ci --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 multi-registry publishing in one job, configure lpm.json > publish.registries and pass nothing — lpm publish walks every configured target.

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.
  • Forgetting permissions: id-token: write for OIDC in GitHub Actions.
  • Caching node_modules/ instead of ~/.lpm/store/. The 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