lpm publish
Validate and publish a package to LPM.dev Registry, npm, GitHub Packages, GitLab, or a custom registry.
Use lpm publish to prepare the current package and upload it to one or more registries.
lpm publish [OPTIONS]
lpm p [OPTIONS]| Mode | Purpose |
|---|---|
--check | Validate the package locally, without scripts or registry requests. |
--dry-run | Prepare the package and check the selected registries without uploads. |
| No preview flag | Prepare and upload the package. |
The default target is LPM.dev Registry. Use --npm, --github, --gitlab, or --publish-registry to publish elsewhere. LPM CLI supports npm-compatible registries without an LPM.dev Registry account.
Quickstart
From the package directory:
lpm publish --npm --check
lpm publish --npm --dry-run --ignore-scripts
lpm publish --npm --yesFrom a subdirectory, lpm publish uses the nearest directory with a package.json, as lpm install does, and prints the directory it chose.
The package needs a name and a valid semantic version. package.json > private: true prevents publication to every registry, including preview modes. This field differs from private distribution on LPM.dev Registry.
For workspace-wide publication, use lpm release publish.
Examples
# Publish with lifecycle scripts in CI
lpm publish --npm --yes --json
# Publish without lifecycle scripts
lpm publish --npm --ignore-scripts --yes
# Publish to two registries
lpm publish --lpm --npm --yes
# Preview GitHub Packages publication
lpm publish --github --dry-run --ignore-scripts
# Publish to a custom npm-compatible registry
lpm publish --publish-registry https://registry.example.com/npm --yes
# Supply an LPM.dev Registry authenticator code without a prompt
lpm publish --lpm --otp 123456 --yes --jsonConfigure publishing with lpm.json
Set the default targets and their names in lpm.json:
{
"publish": {
"registries": ["npm", "lpm"],
"npm": {
"name": "@acme/core",
"access": "public",
"tag": "latest"
},
"lpm": {
"name": "@lpm.dev/acme.core"
}
}
}# Use the configured targets
lpm publish --yes
# Select only npm for this run
lpm publish --npm --yesAny target flag replaces publish.registries. Per-target configuration still applies. An absent or empty target list defaults to LPM.dev Registry.
Field under publish | Behavior |
|---|---|
registries | Array of lpm, npm, github, gitlab, or custom registry URLs. |
lpm.name | LPM.dev Registry name. Defaults to the manifest name. |
npm.name | npm and custom-registry name. Defaults to the manifest name. |
npm.access | public or restricted. Default: public. |
npm.tag | Dist-tag for all npm-compatible targets. Default: latest. |
npm.registry | Endpoint for the npm target. Default: https://registry.npmjs.org. |
npm.otpRequired | Request npm OTP before the first upload attempt. Default: false. |
github.name | GitHub name. Falls back to npm.name, then the manifest name. |
github.access | Falls back to npm.access, then public. |
gitlab.name | GitLab name. Falls back to npm.name, then the manifest name. |
gitlab.access | Falls back to npm.access, then public. |
gitlab.projectId | Required GitLab project ID as a string. |
gitlab.registry | GitLab instance URL. Default: https://gitlab.com. |
There are no publish fields for script consent, minimum quality score, or provenance. Use the relevant flags or provenance configuration described below.
Publish a prerelease or backport
For an npm-compatible prerelease, set an explicit tag in lpm.json:
{
"publish": {
"registries": ["npm"],
"npm": { "tag": "next" }
}
}lpm version 2.0.0-rc.1 --no-git-tag-version
lpm publish --npm --yeslpm version changes the version. Normal publication has no --tag flag. All npm-compatible targets use publish.npm.tag.
An implicit latest also fails if the registry already contains a higher nondeprecated stable version. For a backport, configure an explicit tag such as legacy.
Publish a build directory
In the source package.json, select a directory with its own package manifest:
{
"name": "@acme/core",
"version": "1.0.0",
"publishConfig": { "directory": "dist" }
}lpm publish --npm --check
lpm publish --npm --yesThe directory must remain inside the source package. Absolute paths and parent traversal are rejected.
The selected directory supplies the artifact name, version, and files, including the authored skills in its own lpm-skills/. Lifecycle scripts run from the source package. The source package's publish and provenance configuration remains authoritative. A private source package still blocks publication.
Select targets and names
LPM.dev Registry names must use @lpm.dev/owner.package. GitHub Packages names must be scoped, such as @owner/package.
LPM CLI selects the first configured name in each target's precedence chain, then validates it. An invalid override does not fall back to another name. To publish an @lpm.dev/* manifest elsewhere, configure an alternate target name.
At most five target registries can resolve from one invocation. Duplicate target entries are removed. Different target types that resolve to the same registry and package name cause an error before upload.
CLI targets run in this order: LPM.dev Registry, npm, GitHub Packages, GitLab, then the custom registry. Configured targets keep their configured order.
Custom endpoints require HTTPS. HTTP is accepted only for loopback development registries, such as http://127.0.0.1:4873. URLs cannot contain credentials, a query, or a fragment.
The global --registry selects the LPM.dev Registry endpoint. --publish-registry selects a custom npm-compatible target.
Preview modes
| Operation | --check | --dry-run |
|---|---|---|
| Local packing and validation | Yes | Yes |
| Lifecycle scripts | No | With consent, unless --ignore-scripts |
| Registry authentication and version checks | No | Yes |
| Supplied provenance bundle validation | Yes | Yes |
| Generated provenance | No | No |
| Upload | No | No |
A dry run checks permissions, account limits, and version availability for LPM.dev Registry. For npm-compatible targets, it checks credentials and version availability. It does not reserve a version or prove that generated signing will succeed.
LPM CLI keeps its manifest changes in memory during previews. Dry-run scripts can still change local files.
Lifecycle scripts
Publishing can run six scripts from package.json:
prepublishOnly,prepack, andpreparebefore packing.postpackafter packing.publishandpostpublishafter successful publication or dry-run preparation.
--check skips all lifecycle scripts. For a real publish or dry run, --yes authorizes them and --ignore-scripts skips them. Non-interactive and JSON commands require one of these choices when scripts exist.
lpm publish --npm --dry-run --yes --json
lpm publish --npm --ignore-scripts --yes --jsonEach script has a five-minute timeout. Scripts run in a sandbox that denies outbound network access and removes ordinary inherited secrets. Local tools in node_modules/.bin remain available when a script changes directory.
Windows scripts require a project on a local drive. Protected parent directories and system tools can require administrator sandbox setup. After setup, publish from your normal terminal.
Swift packages
lpm publish --check
lpm publish --yesFor a native Swift package, the archive must contain a root Package.swift. LPM CLI inspects the package with Swift before upload.
LPM.dev Registry independently inspects every root and alternate manifest. Publication requires successful archive and manifest signatures before the Registry saves the release.
See Swift package publication for version-specific manifests, size limits, and signing behavior.
Package contents and validation
The package's files list controls explicit inclusion. package.json, README and license files, and the main, browser, and bin entrypoints enter the archive in every case. Each entrypoint must name its file, and every directory on its path, as the directory above lists it. An entrypoint that reaches one by another spelling the file system resolves, such as another letter case (INDEX.JS for index.js), another Unicode form of an accented letter, or an NTFS short name (ENV~1.LOC for .env.local, LPM~1 for .lpm), stops the publish: a file system that compares names exactly, as Linux does, finds nothing under that name, and the exclusions and secret checks below read names as listed. A root package.json, lpm.json or lpm.config.json stored under another spelling the file system takes for it, such as LPM.json on a case-insensitive file system or a name with a zero-width mark on HFS+ or in a Linux casefold directory, ships under its own name whether or not files lists it, so main, browser, bin, module, types, exports and imports must name it that way (lpm.json): naming it as stored stops the publish, and so does a main that Node completes to it with an extension, such as ./LPM, and so does an exports or imports pattern that can reach it, such as ./*.json, until the file is renamed to lpm.json. A target's query or fragment (?…, #…) isn't part of the file it names. A file selected by its own name is judged by that name, as a copy of it would be. Without files, each directory's .npmignore, or else its .gitignore, excludes files. Symbolic links are never packed.
Some paths never enter the archive, even when files or an entrypoint names them:
| Paths | Location |
|---|---|
.lpm and .git, as directories or files, and .git under any spelling Git reads as one, such as .git. or git~1; node_modules/ outside bundled dependencies | Any depth, bundled dependencies included |
The LPM home, ~/.lpm or the directory that LPM_HOME names | Anywhere in the package, under any name. A package directory that is the LPM home is refused. |
.npmrc, .npmignore, and .gitignore | Any depth |
lpm.lock, lpm.lockb, lpm-changes/, lpm-sources.json, and lpm-agent-skills.json | Package root. These committed files describe the project, not the package. |
package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lock, and bun.lockb | Package root |
Credential files match by name at any depth. Their names, and those in the table above, match regardless of letter case, including letters such as ſ, ẞ, and the Kelvin sign K, which APFS reads as s, ss, and k, and regardless of the characters HFS+ and Linux casefold directories leave out of names, such as zero-width marks:
.envand.env.<name>files, except templates whose name ends in.example,.sample,.template,.defaults, or.vault, such as.env.production.example.envrc,.netrc,_netrc,.pgpass,.pypirc, and.git-credentials- SSH private keys:
id_rsa,id_dsa,id_ecdsa, andid_ed25519 - Cloud keys:
credentials.json,service-account.json, andgcloud-key.json
Without files, publication leaves credential files out. If files selects one, the secret scan stops the publish and names the file.
--check and --dry-run list every archive file with its size. Their JSON output adds tarball_files, an array of path and size entries.
Authored package skills in lpm-skills/ receive format and security checks for every target. Only valid skill files ship, at lpm-skills/ in the archive, whatever files lists. Publication does not edit package.json > files. --allow-secrets does not bypass skill validation.
A root lpm.json ships even when files omits it. The published copy leaves out vault and vaultSync, which identify the author's env and its sync state. If envSchema uses extends or overrides, the published copy holds the resolved schema. Other keys ship unchanged.
LPM CLI rewrites publishable workspace: and catalog: dependencies inside the archive. It also applies target-specific package names there. These changes leave the source manifest unchanged.
The final artifact for every target receives a secret scan, which finds API keys, tokens, and private keys in PEM, OpenSSH, SSH2, PuTTY, and PGP form. This includes bundled dependencies and explicitly included files that Git ignores. The scan reads the whole of JavaScript and TypeScript files; .json, .yml, .yaml, .toml, .cfg, .conf, .ini, and .env files; shell scripts; Python, Ruby, Rust, Go, Java, Kotlin, and Swift sources; .md, .txt, and .xml files; private-key files (.pem, .key, .p8, .pk8, .ppk); files whose names start with .env or end in rc; files under a private, secrets, .secrets, credentials, or .credentials directory; and files without an extension, such as bin/ scripts. It checks the first 64 KiB of every other file for a private key, so a key file stops the publish whatever its name. A file without an extension whose content is binary, such as a prebuilt native executable, is checked that way instead of being read as text. --allow-secrets skips this scan for one invocation, including the credential-file check.
LPM.dev Registry targets also receive a local quality score. --min-score <N> requires that target and accepts 0 through 100. The local score excludes checks that require server data. See lpm quality.
For LPM.dev Registry, lpm.config.json > type accepts package or source. If omitted, the registry infers the classification. Any valid lpm.config.json still activates the configured lpm add workflow.
If published skills match the previous release semantically, LPM.dev Registry returns a review reminder. Line endings and trailing whitespace do not count as changes. JSON includes the reminder in the target's warnings array.
Bundled dependencies
bundledDependencies and its alias bundleDependencies accept an array, true, or false. An array selects named runtime dependencies. true selects every declared dependencies entry. false selects none.
Selected bundles include installed production and optional transitive dependencies when available. Other node_modules content stays excluded.
Run lpm install before publication. Missing bundles, wrong package identities, unsafe links, and paths outside the project or package store cause an error. Hoisted and isolated install layouts are supported.
Archive limits
| Limit | Maximum |
|---|---|
| One source file | 200 MiB |
| Uncompressed tar archive, including headers and padding | 500 MiB |
| Compressed tarball | 500 MiB |
| Archive entries | 100,000 |
| GNU or PAX metadata entries | 100,000 |
| One GNU or PAX metadata record | 1 MiB |
| Encoded archive path | 32 KiB |
Path components, including package/ | 256 |
The package/ prefix leaves 255 components for a project-relative path. Target-specific rewrites and bundled dependencies use the same limits. Other registries can impose smaller limits.
LPM.dev Registry accepts compressed tarballs up to 100 MiB. It accepts publish requests up to 150 MiB, which hold the tarball in base64 with the metadata of the package. If a publish includes LPM.dev Registry, LPM CLI refuses a larger tarball before it publishes to any target. A --provenance publish adds its Sigstore bundle to the request two times, about 10 KB each. As a result, LPM CLI keeps 1 MiB of the limit free for it.
When LPM.dev Registry refuses a publish
A publish to LPM.dev Registry and another target first asks LPM.dev Registry, and sends the size of the tarball. If LPM.dev Registry refuses the publish for good, for permission, payment, or size (HTTP 402, 403, or 413), the publish stops before any target publishes. These refusals are for:
- The storage, private-package limit, or billing of the account
- The 2FA rule or IP allowlist of an organization
- A scope that the account does not own
- The size of the tarball
- The permission of the token
Each target handles other answers at its own upload. One of these answers is a version that LPM.dev Registry already has, for example after a partial publish. LPM.dev Registry gives the VERSION_EXISTS code, or an older LPM.dev Registry gives only its message. The LPM.dev target then reports that answer and does not upload. The other targets publish.
Each target also handles these answers at its own upload:
- A request that LPM.dev Registry cannot read
- An unknown package
- A rate limit
- A server error
- An answer that LPM CLI cannot read
After a rate limit, a timeout, or a server error, LPM CLI tries the request again first.
Before any target publishes, the same publish then completes the second factor of LPM.dev Registry, unless LPM.dev Registry already has the version. If 2FA is on, it asks for the code. With --json and no --otp, it publishes to no target. A malformed --otp, or a token that LPM.dev Registry refuses, also stops it at this point.
If LPM CLI cannot read the account, for example while LPM.dev Registry is unavailable, the other targets still publish. The LPM.dev target tries again and reports its own result.
This first question counts the tarball. At the upload, LPM.dev Registry also counts the content that it derives from the tarball: the README, skill content, and for a Swift package, its source archive, raw Package.swift, and manifest bundle. As a result, LPM.dev Registry can still refuse a publish at its upload if the publish is within that much of the limit. API docs and LLM context, which it derives later, also count toward storage. --dry-run asks the same question and fails on any refusal.
Credentials and OTP
See Authentication for login commands and token storage.
| Target | Credential priority |
|---|---|
| Public npm | Trusted Publishing for uploads → NPM_TOKEN → stored npm token → effective .npmrc token |
| GitHub Packages | GITHUB_TOKEN → gh auth for github.com → stored fallback token |
| GitLab.com Packages | GITLAB_TOKEN → CI_JOB_TOKEN → glab auth → stored fallback token |
| Self-managed GitLab | Token stored for the full project registry endpoint |
| Custom registry | Token stored for the selected registry URL |
npm configuration merges global, user, then project or workspace configuration. Later assignments can override or clear earlier values.
Repository configuration does not redirect default npm credentials to a custom endpoint. Store credentials for the full registry URL:
lpm login --login-registry https://registry.example.com/npm --token '<token>'
lpm login --login-registry https://gitlab.example.com/api/v4/projects/12345/packages/npm --token '<token>'Trailing slashes do not create a separate credential scope. Different registry paths do.
For LPM.dev Registry MFA, use --otp <CODE> with exactly six ASCII digits. This flag does not supply npm OTP.
npm-compatible publishing supports browser-based and interactive OTP challenges. publish.npm.otpRequired requests OTP before the first upload attempt. For non-interactive npm publication, use an automation token or Trusted Publishing.
Publish redirects preserve an upload body only within the original registry directory and origin. A 303 redirect drops the body. See the redirect transport policy.
npm Trusted Publishing
For the public npm registry, LPM CLI can exchange CI identity for a short-lived upload token:
- GitHub Actions: grant
permissions: id-token: write. - Other supported npm providers: set
NPM_ID_TOKENwith audiencenpm:registry.npmjs.org.
If the exchange fails and a normal npm token exists, LPM CLI uses that token. Otherwise, the exchange error stops publication.
Custom targets, GitHub Packages, and GitLab use token authentication. Trusted Publishing also supports lpm stage publish, but not stage inspection or approval commands.
Provenance
lpm publish --npm --provenance --yes
lpm publish --npm --provenance-file bundle.sigstore --yes
lpm publish --npm --no-provenance --yesGenerated provenance requires a supported CI identity with audience sigstore. GitHub Actions uses id-token: write. GitLab uses SIGSTORE_ID_TOKEN from an id_tokens entry. LPM_GITLAB_OIDC_TOKEN remains a legacy alias.
Signing or transparency-log failure stops the affected upload. LPM CLI does not silently publish that target without requested provenance.
npm-compatible provenance requires effective public access. restricted access fails before signing, file validation, or upload.
--provenance-file accepts a verified Sigstore bundle up to 10 MiB. Its subject and SHA-512 digest must match the final target artifact. Any target selection containing LPM.dev Registry rejects this flag, including mixed selections.
The three provenance flags are mutually exclusive. Configuration priority is:
- CLI provenance flags.
package.json > publishConfig.provenanceorpublishConfig["provenance-file"].NPM_CONFIG_PROVENANCEorNPM_CONFIG_PROVENANCE_FILE, including lowercase aliases.- Project or workspace-root
.npmrc. - User
.npmrc. - Global npm configuration.
For the environment Boolean, only exact lowercase true enables signing. An empty value is unset. Other nonempty values disable it. Conflicting uppercase and lowercase values cause an error.
Boolean and file settings cannot combine across npm configuration layers. Repository-configured bundle paths must be relative and stay inside the project. --no-provenance disables configured signing for one run.
Publication review and --wait
LPM.dev Registry packages use private distribution by default. Free, Pool, and Marketplace require explicit opt-in and publication review. Private versions can enter a processing state or security cooldown.
lpm publish --lpm --wait --yes
lpm publish --lpm --wait --wait-timeout 1200 --yes --json--wait waits for the uploaded version to become available under its distribution rules. It requires an LPM.dev Registry target and cannot combine with --check or --dry-run.
LPM CLI polls every three seconds. The default timeout is 1200 seconds. --wait-timeout requires --wait and accepts 10 through 3600 seconds.
All target uploads finish before the wait starts. Timeout, polling failure, manual review, rejection, quarantine, unpublishing, or an unknown state causes a nonzero exit. These results do not undo the upload.
A change from private to public distribution reviews the latest eligible version. Earlier private history stays publisher-only. Future versions require review individually. See LPM.dev package distribution for coverage and dependency requirements.
Recover from failures
Uploads run sequentially and collect per-target results. Successful uploads remain published if another target fails. Retry only the failed targets:
# Retry GitHub after npm succeeded
lpm publish --github --yes --jsonA post-upload lifecycle failure or publication-wait failure can occur after successful upload. Check the registry before retrying. To publish different contents, choose a new version with lpm version.
If an LPM.dev Registry connection fails after upload, LPM CLI checks the stored artifact before retrying. A matching artifact can complete without another upload. A later command can instead report that the version already exists.
A real publish can recover pending local manifest updates from version or release commands. Previews stop when this recovery is required. If recovery refuses changed files, inspect the reported paths and restore the original directories before retrying.
JSON output
Use --json for local checks and automation:
lpm publish --npm --check --json
lpm publish --npm --yes --jsonCheck output includes success, check, targets, and quality. Real publication reports per-target upload results. publication_wait records the wait result separately from upload success. Earlier preparation failures use the standard JSON error format.
The quality result includes score, max_score, earned_points, applicable_points, and checks. Each check reports passed, failed, or not_evaluated in its evaluation field.
Flags
| Flag | Description |
|---|---|
--check | Validate locally without scripts, authentication, registry requests, or uploads. |
--dry-run | Prepare and perform remote preflight without uploads. |
-y, --yes | Accept confirmation and authorize lifecycle scripts. |
--ignore-scripts | Skip package lifecycle scripts. |
--wait | Wait for LPM.dev Registry publication availability. |
--wait-timeout <SECONDS> | Wait limit, from 10 to 3600. Default: 1200. Requires --wait. |
--otp <CODE> | Six-digit LPM.dev Registry authenticator code. |
--min-score <N> | Minimum local quality score, from 0 to 100. Requires an LPM.dev Registry target. |
--allow-secrets | Skip the final-artifact secret scan. |
--npm | Select npm, including its configured endpoint. |
--lpm | Select LPM.dev Registry. |
--github | Select GitHub Packages. |
--gitlab | Select GitLab Packages. |
--publish-registry <URL> | Select a custom npm-compatible registry. |
--provenance | Require generated Sigstore provenance. |
--no-provenance | Disable configured provenance for this run. |
--provenance-file <PATH> | Attach a verified bundle to npm-compatible targets. |
All modes support --json and the other global flags.
See also
lpm version— change the package versionlpm release— coordinate workspace releaseslpm stage— review npm uploads before promotionlpm quality— inspect the quality score- Authentication — credentials and login commands
- Publish configuration — all
lpm.jsonfields