Skip to content

feat: track requested packages in a manifest to fix dep resolution (#88)#89

Open
jhheider wants to merge 2 commits into
mainfrom
add-manifest-consistency
Open

feat: track requested packages in a manifest to fix dep resolution (#88)#89
jhheider wants to merge 2 commits into
mainfrom
add-manifest-consistency

Conversation

@jhheider

Copy link
Copy Markdown
Contributor

pkgm outdated/update crashed with cannot intersect: ^1.3.2 && =1.3.1
whenever two incompatible versions of a shared dependency were installed
(e.g. node pulls zlib 1.3.2 while python, via uv, pins zlib =1.3.1), and
installs resolved each package in isolation — so a later pkgm i node would
relink zlib to its own pick and break python, order-dependently.
The root problem is that pkgm had no record of intent: it couldn't tell a
package you asked for from one dragged in transitively, so it re-seeded the
resolver from the flattened on-disk state. Introduce a manifest of explicitly
requested packages and resolve against it.
Manifest:

  • per prefix: ${XDG_CONFIG_HOME:-~/.config}/pkgm/manifest.toml for user
    installs, /usr/local/etc/pkgm/manifest.toml for the system prefix.
  • TOML, hand-editable: a requested package carries a semver range (a bare
    pkgm i node major-locks to what installed; node@22 keeps ^22); a
    transitive dependency is dep. comments and edits to untouched lines survive
    rewrites.
  • install and uninstall keep it in step; a "boot" check refuses to run on
    an unfinished hand-edit.
    Resolution:
  • install folds the manifest's requested packages into the pkgx query, so a
    shared dependency is computed once across everything you've asked for
    (+node +uv → zlib 1.3.1, satisfying both) and links coherently regardless
    of install order.
  • outdated/update seed hydrate from the requested (non-dep) entries; an
    unreconcilable seed is dropped with a specific warning instead of aborting
    the whole command. with no manifest, behavior is unchanged.
    Incidental fixes surfaced while testing the multi-version state error: Uncaught (in promise) Error: cannot intersect: ^1.3.2 && =1.3.1 #88 creates:
  • symlink_with_overwrite: use lstat, not exists() (which follows links), so a
    broken symlink is overwritten instead of crashing the mirror with EEXIST.
  • uninstall: actually populate the dedupe set and collect via lstat, so a
    package with two installed versions doesn't crash on a shared leaf or leave
    versioned symlinks orphaned.
    Tests: new macOS manifest CI job covering record/recreate/uninstall, the
    no-uncaught-exception guarantee in both install orders, and that a shared dep
    settles on the strict pin regardless of order; hyperfine test updated for the
    new pin-respecting semantics.

`pkgm outdated`/`update` crashed with `cannot intersect: ^1.3.2 && =1.3.1`
whenever two incompatible versions of a shared dependency were installed
(e.g. node pulls zlib 1.3.2 while python, via uv, pins zlib =1.3.1), and
installs resolved each package in isolation — so a later `pkgm i node` would
relink zlib to its own pick and break python, order-dependently.
The root problem is that pkgm had no record of intent: it couldn't tell a
package you asked for from one dragged in transitively, so it re-seeded the
resolver from the flattened on-disk state. Introduce a manifest of explicitly
requested packages and resolve against it.
Manifest:
- per prefix: `${XDG_CONFIG_HOME:-~/.config}/pkgm/manifest.toml` for user
  installs, `/usr/local/etc/pkgm/manifest.toml` for the system prefix.
- TOML, hand-editable: a requested package carries a semver range (a bare
  `pkgm i node` major-locks to what installed; `node@22` keeps `^22`); a
  transitive dependency is `dep`. comments and edits to untouched lines survive
  rewrites.
- `install` and `uninstall` keep it in step; a "boot" check refuses to run on
  an unfinished hand-edit.
Resolution:
- `install` folds the manifest's requested packages into the pkgx query, so a
  shared dependency is computed once across everything you've asked for
  (`+node +uv` → zlib 1.3.1, satisfying both) and links coherently regardless
  of install order.
- `outdated`/`update` seed hydrate from the requested (non-`dep`) entries; an
  unreconcilable seed is dropped with a specific warning instead of aborting
  the whole command. with no manifest, behavior is unchanged.
Incidental fixes surfaced while testing the multi-version state #88 creates:
- `symlink_with_overwrite`: use lstat, not exists() (which follows links), so a
  broken symlink is overwritten instead of crashing the mirror with EEXIST.
- `uninstall`: actually populate the dedupe set and collect via lstat, so a
  package with two installed versions doesn't crash on a shared leaf or leave
  versioned symlinks orphaned.
Tests: new macOS `manifest` CI job covering record/recreate/uninstall, the
no-uncaught-exception guarantee in both install orders, and that a shared dep
settles on the strict pin regardless of order; hyperfine test updated for the
new pin-respecting semantics.
Copilot AI review requested due to automatic review settings June 28, 2026 19:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a per-prefix TOML manifest to persist “user intent” (explicitly requested packages vs transitive deps) so installs and outdated/update resolve coherently across all requested packages, preventing order-dependent dependency conflicts like #88.

Changes:

  • Introduces a manifest (manifest.toml) with helpers to read/update it and to combine requested specs during installs.
  • Updates outdated/update to seed hydration from requested manifest entries (dropping unreconcilable seeds with warnings instead of crashing).
  • Adds uninstall + symlink robustness fixes and a new macOS CI job to exercise manifest behaviors.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 3 comments.

File Description
pkgm.ts Adds manifest implementation + resolver seeding changes; improves symlink/uninstall robustness.
deno.lock Locks new std/toml dependency (and related std specifiers).
.github/workflows/ci.yml Extends CI and adds a macOS “manifest” job to cover #88 scenarios and manifest semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkgm.ts
Comment thread pkgm.ts
Comment thread pkgm.ts Outdated
this should closes #86 [sic]

done
Copilot AI review requested due to automatic review settings June 28, 2026 20:38
@jhheider jhheider force-pushed the add-manifest-consistency branch from 5b61f81 to 91e2863 Compare June 28, 2026 20:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 3 changed files in this pull request and generated 4 comments.

Comment thread pkgm.ts
Comment on lines +345 to +349
// HOME at their home so it doesn’t cache back under /root/ where they
// couldn’t reach it on the next invocation.
const home = user_home(sudo_user);
if (home) env.HOME = home;
args.unshift("-u", sudo_user, pkgx);
Comment thread pkgm.ts
Comment on lines +1046 to +1052
const pending = new Map(Object.entries(set));
const removing = new Set(remove);
const key_re = /^\s*"([^"]+)"\s*=/;

const out: string[] = [];
for (const line of text.split("\n")) {
const project = line.match(key_re)?.[1];
Comment thread pkgm.ts
Comment on lines +1086 to +1094
for (const arg of args) {
try {
const found = await hooks.usePantry().find(arg);
if (found.length == 1) new_projects.add(found[0].project);
} catch {
// unresolved project name; the worst case is a duplicate spec, which
// pkgx reconciles against the identical project anyway.
}
}
Comment thread pkgm.ts
Comment on lines 845 to 847
for (const pkgspec of update_list) {
const pkg = utils.pkg.parse(pkgspec);
console.log(
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

error: Uncaught (in promise) Error: cannot intersect: ^1.3.2 && =1.3.1

2 participants