Documentation menu

Publishing

flog publish uses a prepare-then-execute model. It completes the full plan before the first registry upload, so a manifest, lifecycle, packing, git, or registry-state failure in any selected package leaves every package unpublished. See the flog publish command summary for its principal preflight, confirmation, and output flags.

Selection and order#

Without --recursive or --workspace, flog publishes the package containing the selected directory. Private packages are rejected. --workspace selects by exact package name, workspace directory, owned file, or workspace path glob; --recursive separately selects every workspace. Exact and glob selectors cannot be mixed, and duplicate aliases or overlapping globs are errors. Private workspace packages matched by broad path globs or a plain recursive selection are summarized by count in human plans and retain their package name, importer path, and private reason in JSON plans and summaries. An exact workspace selector is a direct request and remains an error. A broad selection containing only private workspaces is also an error.

Selected packages are ordered dependency-first using the workspace graph. Before running package lifecycles or packing, flog checks each selected exact name and version in the registry. An existing version is reported as already-published and skipped without building a local artifact, comparing integrity, or uploading. This is an identity check, not proof that current local contents reproduce the published tarball. Changed contents require a new version; flog never attempts to replace an existing version.

Preflight and lifecycle#

For every selected package, flog first:

  1. validates every project dependency declaration against flog.lock, then validates package identity, version, requested tag and access, git state, and workspace selection;
  2. reads registry baselines, classifies exact existing versions, rejects an incoherent workspace release set, and reports newer compatible direct dependency versions as advisory warnings.

For each genuinely unpublished version, flog then:

  1. runs prepublishOnly;
  2. packs the package, which runs prepack, prepare, and postpack, rewrites workspace: ranges, and stages the exact tarball to upload;
  3. validates the exact packed dependency declarations against the reviewed lock after lifecycle changes and workspace: rewriting;
  4. revalidates the release set against post-lifecycle manifests and the published metadata of skipped versions; and
  5. displays the ordered publish candidates and a concise plan summary. A human dry run shows a short terminal-only status line for each candidate while it builds, then replaces it with the version transition, tag, and access intent. Detailed package and release-set outcomes remain available with --json.

Only after the complete plan succeeds and is confirmed does flog upload staged artifacts sequentially. A successful upload runs publish and postpublish. Artifacts are never rebuilt between the plan and upload. Flog records each accepted upload as pending verification and continues with the next package without waiting for metadata propagation. After uploading, it verifies accepted versions, artifact integrity, and requested visibility with fresh registry metadata reads. Checks are sequential in publication order, and each accepted package gets its own window of up to 60 seconds with bounded backoff and request counts. The first package that exhausts its window becomes accepted-pending-verification; later accepted packages become accepted-not-checked, and verification stops. An upload or pre-upload safety failure still stops later uploads; already accepted versions are reconciled and reported separately. Neither pending status is evidence of a failed upload, and verification never retries an upload. Inspect the registry before recommending consumption or retrying.

If caller cancellation stops verification instead, later accepted packages are also accepted-not-checked, but their summary and human output preserve the cancellation reason rather than claiming that an earlier package timed out.

A source or packed manifest/lock mismatch is always fatal, including dry-run, and no force or acknowledgement option bypasses it. Lifecycle scripts therefore cannot make an unreviewed dependency change eligible for upload. A newer registry version that still satisfies a selected package's declared range is different: the reviewed lock remains authoritative, publication continues, and the deterministic warning reports the locked and available versions with flog upgrade guidance. Exact declarations and ranges with no newer compatible version produce no warning.

Workspace release sets#

Publication preflight computes a release closure from public workspace dependencies. If a selected package crosses a compatibility line and a published dependent's range excludes the proposed version, that dependent must be included with its own compatibility-line bump. The check propagates through transitive dependents. For SemVer, compatibility lines are a major version, a minor version within 0.x, or an individual 0.0.x version.

An already-published dependent is exempt from this backward release obligation when its version at the requested registry tag has a non-empty npm deprecation message. A local package.json deprecation flag alone is not authoritative. The exemption is recorded in JSON release-set diagnostics; it does not excuse a newly published dependent from compatibility or dependency availability checks. Registry metadata, including deprecation, is rechecked before uploads.

Flog also rejects packed workspace dependencies whose ranges are not satisfied by an existing registry version or an earlier package in the publication plan. It never selects packages or edits versions automatically. Use --allow-incomplete-release-set only to accept a reviewed exception; the override remains visible in human plans; every affected edge remains in JSON reports. --yes and git overrides do not bypass release-set validation. Development-only workspace edges are reported in JSON but do not require publication. Human-readable plans lead with blockers and proposed remedies, list only publish candidates, and summarize already-published and private packages by count. --json preserves every informational edge and skipped package for auditing or tooling.

See workspace release sets for compatibility-line rules, dependency-chain examples, availability checks, and override behavior.

Immediately before every upload, flog reads that package's registry metadata again. It aborts if the prepared version appeared, if latest changed, or if a registry-reported access value changed after preflight. Registry baselines used by release-set analysis are also checked before the first mutation and between uploads. When an OTP challenge occurs, the same check is repeated after the OTP is obtained and before the authenticated retry. A mutating request that starts web authentication is not automatically replayed; after authentication, flog rechecks metadata and explicitly retries it. These checks narrow the stale-plan window, but npm-compatible publication endpoints do not provide a portable conditional update that atomically compares the old dist-tag or access while publishing a version. A registry change in the final interval between this read and its upload can therefore still race; flog does not claim race-free latest or access safety.

Lifecycle scripts and packages run sequentially. Cancellation during a prompt, web login, OTP request, lifecycle script, or upload prevents subsequent uploads.

Registry authentication#

Real publication requires a credential accepted by the selected npm registry. Use flog login or configure a usable npm token in npmrc before publishing when the registry does not return an interactive web-authentication challenge. Flog does not turn an ordinary unauthorized response into login automatically.

Flog requests web authentication on publication uploads with npm-auth-type: web and npm-command: publish. When npm returns login and completion URLs, flog prints the readable login URL. If the registry returns a plain OTP challenge without those URLs, flog still prompts for an OTP rather than guessing a browser URL. In a supported interactive terminal, press Enter to open it with the platform browser, or type anything to continue with the printed link. Browser-launch failure leaves that manual link available. Redirected input or output and unsupported or TERM=dumb terminals print the link without prompting. If redacting a known configured credential changes the login URL and it cannot be opened safely, flog fails before polling with instructions to authenticate through the registry and retry. Flog polls only the registry-provided completion URL; neither credentials nor the completion URL are printed.

A web-authenticated mutating request is never replayed implicitly. The completion code for a publication challenge is sent as npm-otp on the revalidated retry, not saved as a new login token. After authentication, flog revalidates the prepared version, tags, access, and release-set metadata, then makes one explicit retry. A plain npm EOTP challenge prompts interactive users for a one-time password; automation may set NPM_CONFIG_OTP. If an EOTP response also carries web-authentication URLs, those URLs take precedence over a manual OTP prompt. Cancellation stops polling or prompting and prevents the explicit retry.

Tags and confirmation#

Stable versions default to latest. A prerelease requires an explicit non-latest tag, for example:

flog publish --recursive --tag next

--tag latest is rejected for every prerelease. For stable versions, flog also rejects a latest update whose SemVer precedence is lower than or equal to the registry's current latest version. An exact version that is already published is skipped even when it is equal to or older than latest, because skipping it does not move any tag.

Interactive publication asks for confirmation after displaying the plan. Automation must deliberately pass --yes; redirected or otherwise noninteractive input is never treated as confirmation.

Package access#

For a first publication, flog follows npm defaults: unscoped packages are public, while scoped packages are restricted. Use an explicit access level when the default is not the intended registry visibility:

flog publish --recursive --access public --tag next

One --access value applies to every selected package. public is valid for mixed scoped and unscoped workspaces; restricted is rejected for any unscoped package during initial validation, before lifecycle scripts or registry requests. For an existing scoped package with no option, flog preserves a known registry-reported public or restricted value. If the registry does not report existing visibility, the plan says preserve and flog omits the access field rather than risk requesting a downgrade to the scoped default.

The npm-compatible upload document carries the requested top-level access field except for that preserve case. Requested access is included in every plan entry, confirmation display, JSON summary, and summary file.

A successful upload response proves only that the registry accepted a mutation; it does not prove that the version is visible or that the registry applied the access field. Human output therefore records a durable publishing line before each upload and a separate registry accepted line after its response. Uploads remain sequential and dependency-first. Flog finishes all candidate uploads before making any verification read, so metadata propagation never delays a later upload. Authentication, OTP, and lifecycle output remains separate from these package lines.

After the upload phase, flog verifies accepted packages sequentially in publication order. Each package receives its own window of up to 60 seconds and fresh metadata requests with bounded, spaced retries. A terminal may show one transient next-retry countdown; redirected output uses sparse durable retry lines and no terminal control characters. Verification never resends an upload. Flog reports published only when exact artifact metadata and the observed public/restricted visibility match the access intent. An accepted upload with different visibility is published-access-mismatch; unavailable verification or an unknown prior value under preserve is published-access-unverified. Both outcomes return an error, record access intent and observed access separately, and warn that the accepted publication may be irreversible. publish and postpublish still run because the registry accepted the package.

If a registry rejects the field, flog reports the ordinary failed upload. If it accepts but ignores the field, flog never silently substitutes visibility or reports confirmed success. A confirmed public result for requested restricted access fails immediately. Authenticated metadata that explicitly reports restricted access for a requested-public upload also fails immediately; when that field is absent or consistent, missing anonymous visibility is allowed only during the bounded propagation window. If the first package to exhaust its window remains invisible, it becomes accepted-pending-verification and later accepted packages become accepted-not-checked; they did not time out and were not proved failed. Upload, cancellation, or postpublish failure stops later uploads as not-attempted, after which already accepted packages are reconciled without retrying an upload. Dry runs show the access intent but remain anonymous and never mutate package visibility.

Dry runs, git checks, and summaries#

--dry-run performs lifecycle and packing for unpublished versions plus all registry-state and safety checks, but does not upload or authenticate. Anonymous registry metadata reads are bounded to six concurrent requests. It does not run lifecycle or packing work for versions classified as already published. Metadata requests omit configured and session credentials and cannot start web authentication.

By default the worktree must be clean and the current branch must be master. main is treated like any other non-canonical branch. --allow-dirty and --allow-branch override those checks individually. --no-git-checks disables both. Every active override is shown as a warning in the plan.

--dry-run --json runs without terminal progress and writes one machine-readable summary, including plan safety warnings, to stdout when complete. A blocked dry run prints full plan diagnostics to stderr. For a real publish with --json, the plan is still written to stderr before mutation and the outcome summary to stdout. Use --summary <file> to atomically record the same summary in a JSON file, including failed, published-access-mismatch, published-access-unverified, accepted-pending-verification, accepted-not-checked, and not-attempted package statuses after a partial or unverifiable publication. A rerun identifies already-present versions from registry metadata before rebuilding only the remaining versions.

After published-access-unverified, inspect the registry before retrying: the upload may already be irreversible. A safe rerun classifies that exact version as already-published and continues with remaining packages rather than uploading it again. In recursive publication, later packages remain not-attempted only when the upload phase stops before reaching them. A verification timeout happens after uploads and leaves later accepted packages accepted-not-checked.

The web-auth, OTP, cancellation, and propagation paths are covered with protocol-level and end-to-end mock registries. They are not a substitute for a live npm canary. Before using a new Flog release for rcompat, obtain explicit operator approval for a disposable scoped public package and verify the actual credential/2FA challenge, public access, metadata propagation, and final summary. Until that gate is completed, live npm publication remains an outstanding release risk.