feat: track requested packages in a manifest to fix dep resolution (#88)#89
Open
jhheider wants to merge 2 commits into
Open
feat: track requested packages in a manifest to fix dep resolution (#88)#89jhheider wants to merge 2 commits into
jhheider wants to merge 2 commits into
Conversation
`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.
Contributor
There was a problem hiding this comment.
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/updateto 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.
this should closes #86 [sic] done
5b61f81 to
91e2863
Compare
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 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 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 on lines
845
to
847
| for (const pkgspec of update_list) { | ||
| const pkg = utils.pkg.parse(pkgspec); | ||
| console.log( |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
pkgm outdated/updatecrashed withcannot intersect: ^1.3.2 && =1.3.1whenever 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 nodewouldrelink 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:
${XDG_CONFIG_HOME:-~/.config}/pkgm/manifest.tomlfor userinstalls,
/usr/local/etc/pkgm/manifest.tomlfor the system prefix.pkgm i nodemajor-locks to what installed;node@22keeps^22); atransitive dependency is
dep. comments and edits to untouched lines surviverewrites.
installanduninstallkeep it in step; a "boot" check refuses to run onan unfinished hand-edit.
Resolution:
installfolds the manifest's requested packages into the pkgx query, so ashared dependency is computed once across everything you've asked for
(
+node +uv→ zlib 1.3.1, satisfying both) and links coherently regardlessof install order.
outdated/updateseed hydrate from the requested (non-dep) entries; anunreconcilable 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 abroken symlink is overwritten instead of crashing the mirror with EEXIST.
uninstall: actually populate the dedupe set and collect via lstat, so apackage with two installed versions doesn't crash on a shared leaf or leave
versioned symlinks orphaned.
Tests: new macOS
manifestCI job covering record/recreate/uninstall, theno-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.