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/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://lpm.dev/install.sh | sh
- run: echo "$HOME/.lpm/bin" >> $GITHUB_PATH2. Authenticate reproducibly
For private packages on lpm.dev, two approaches:
OIDC (recommended for GitHub Actions, GitLab CI)
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-integritylpm 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 tokenStore 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.dev — lpm 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 equivalentPrints 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| Flag | What it does in CI |
|---|---|
--offline | No network. Resolves entirely from lpm.lock + the global store. Errors if anything is missing — turns "lockfile drift went unnoticed" into a hard fail. |
--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 | Skip editor auto-integration. CI has no editor. |
--no-security-summary | Skip 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 --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 --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-none6. 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
@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.- Forgetting
permissions: id-token: writefor OIDC in GitHub Actions. - Caching
node_modules/instead of~/.lpm/store/. The 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— generate.npmrcfor CIlpm ci— env-var emission, OIDC workflow generationlpm install --offline— reproducible installslpm audit— security gating- Exit codes — what each non-zero code means
- Environment variables —
LPM_TOKEN,LPM_OIDC_TOKEN