Skip to content

Feature: support lockfile-only resolution (apm lock) #975

@danielmeppiel

Description

@danielmeppiel

Problem

apm install today is a single, all-or-nothing command: it resolves dependencies, writes apm.lock.yaml, and deploys files into target directories (.github/, .cursor/, .opencode/, etc.) in one shot. There is no mode that stops after the lockfile is written.

This blocks the declarative dotfiles workflow that users of Nix Home Manager, chezmoi, GNU Stow, yadm, and similar managers expect:

  1. Edit apm.yml ("I want these packages")
  2. Resolve + lock → write apm.lock.yaml
  3. Commit only apm.yml + apm.lock.yaml to a dotfiles repo (no generated .github/ mirror)
  4. On a new machine, activation (home-manager switch, chezmoi apply, etc.) calls apm install to deploy files into the right places from the pinned lockfile

Today step 3 is impossible without dirtying the working tree with deployed targets the user doesn't want under version control.

The npm analogue would be npm install --package-lock-only: resolve, write lockfile, touch nothing else.

Why

  • Dotfiles users (chezmoi / Home Manager / Stow / yadm) version-control config and deploy on activation; they need lockfile-only commits.
  • CI/CD pipelines that want a "resolve" step separated from a "deploy" step (e.g. a lockfile-update PR bot commits the new lockfile without running integrators).
  • Monorepo maintainers who want to refresh lockfiles across workspaces without writing target files into every project tree.

The underlying principle is separating dependency resolution from deployment side effects — a fundamental composability property other package managers already provide.

Current behaviour

Mode Writes lockfile? Writes targets?
apm install --dry-run No No
apm install Yes Yes
apm install --lockfile-only (proposed) Yes No

There is no mode in the middle. --dry-run short-circuits before persisting anything (src/apm_cli/install/pipeline.py); regular install runs the integration phase unconditionally after the lockfile phase.

Proposed surface (open for discussion)

Two reasonable shapes — please flag preference:

A. Flag on apm installapm install --lockfile-only

  • Mirrors the npm install --package-lock-only mental model
  • Skips integration phases when set
  • Smallest surface area

B. Subcommandapm lock

  • Mirrors cargo generate-lockfile, pnpm lock
  • Cleaner mental model for "I just want the lockfile"
  • Could grow to apm lock --update, apm lock --check, etc.

Implementation either way is small: skip the integration phase when the new mode is active, keep the lockfile phase. Idempotency, exit codes, and --no-policy / --no-cache flags would behave identically to today's apm install.

Acceptance criteria

  • Decide between --lockfile-only flag vs apm lock subcommand (UX call)
  • Implement: skip integration phases when the mode is active
  • apm.lock.yaml is written deterministically and matches what apm install would have produced
  • No target files (.github/, .cursor/, .opencode/, etc.) are modified
  • Existing --dry-run, --verbose, --no-policy, scope flags continue to work
  • Test: install in lockfile-only mode, then run plain apm install from the same lockfile on a different machine and confirm parity
  • Docs: Starlight cli-commands.md + apm-guide commands.md updated
  • CHANGELOG entry under [Unreleased]

Use case from @ReoHakase (context)

Use case from dotfiles / Home Manager style setups: I want to commit only the APM manifest and lockfile, then let activation run user-scope install from that pinned state. Today there does not seem to be a lockfile-only command: --dry-run does not write apm.lock.yaml, and normal install writes targets.

comment on #898

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/cliCLI command surface, flags, help text (cross-cutting).area/lockfileLockfile schema, per-file provenance, integrity hashes, drift detection.priority/highShips in current or next milestonestatus/acceptedDirection approved, safe to start work.status/triagedInitial agentic triage complete; pending maintainer ratification (silence = approval).theme/portabilityOne manifest, every target. Multi-target deploy, marketplace, packaging, install.type/featureNew capability, new flag, new primitive.

    Type

    No type

    Projects

    Status

    Todo

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions