lib-management.dep-fetcher v0.1.0: closure walk + conflict + state-check
Pure-Lua manifest-walker for the dep-fetcher slice 5. Reads a module's manifest.module, walks the transitive lib-dep tree, detects pin conflicts (hard-fail), and uses lib-core.git to ensure every lib in the closure is at the pinned v<X.Y.Z> tag. State-check covers cases A-E from the dep-fetcher architecture spec: missing (clone), clean (no-op), dirty (warn), wrong-tag (silent checkout), broken (error). Ships an inline JSON decoder (~80 lines, deterministic, no deps) because the engine has no generic engine.json.decode binding yet — engine.asset.load_json is sandboxed to the asset-tree and cannot read fixture/install paths. Stop-gaps documented in the lib (resolved by slice 6): - opts.install_root substitutes for engine.install_root() binding - gitea_base hardcoded; slice 6 sources from engine.json via engine.config(key) Gitea-slug derivation handles the .git-suffix exception: lib-core.git maps to sporel-lib-core.git-lib because Gitea reserves the *.git suffix pattern. Encoded as generic predicate so future .git-suffixed libs work without code change.
This commit is contained in:
91
README.md
Normal file
91
README.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# lib-management.dep-fetcher
|
||||
|
||||
Pure-Lua manifest-walker + dep-closure resolver. Reads a module's
|
||||
`manifest.module`, walks the transitive lib-dep tree, detects pin
|
||||
conflicts, then uses `lib-core.git` to ensure every lib in the closure
|
||||
is at the pinned `v<X.Y.Z>` tag locally — cloning missing libs,
|
||||
checking out wrong-tag libs, surfacing dirty libs as warnings.
|
||||
|
||||
Slice 5 of the dep-fetcher plan
|
||||
(`sporel-meta/docs/superpowers/plans/2026-05-30-dep-fetcher-implementation.md`).
|
||||
|
||||
## API
|
||||
|
||||
```lua
|
||||
local depf = require("lib-management.dep-fetcher")
|
||||
|
||||
-- Primary entry point
|
||||
local result = depf.ensure_for_module_at(manifest_path, opts)
|
||||
|
||||
-- Convenience wrapper for install-layout resolution (slice 6: switches
|
||||
-- to engine.install_root() once that binding lands).
|
||||
local result = depf.ensure_for_module(module_id, opts)
|
||||
```
|
||||
|
||||
### `opts`
|
||||
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `install_root` | string | Root of `<staged>/libs/`. Falls back to env `SPOREL_INSTALL_ROOT`. Slice 6 will add `engine.install_root()` and drop this stop-gap. |
|
||||
| `check_only` | bool | Skip all git operations. Closure walk + conflict detection only — used by unit tests and by the launcher's dep-diagnose surface. |
|
||||
| `gitea_base` | string | Override base URL (default `https://git.davoryn.de/sporel`). Slice 6 will source from `engine.json` via `engine.config(key)`. |
|
||||
|
||||
### `result`
|
||||
|
||||
```lua
|
||||
{
|
||||
ok : bool, -- false if conflict or error
|
||||
conflicts : { {lib_id, pins=[{source,version}]}, ... },
|
||||
warnings : { {lib_id, kind="dirty", describe}, ... },
|
||||
errors : { {lib_id, kind, message}, ... },
|
||||
closure : { {source, lib_id, version}, ... },
|
||||
}
|
||||
```
|
||||
|
||||
`errors[].kind ∈ { "config", "manifest-read", "net-fail",
|
||||
"describe-fail", "checkout-fail" }`.
|
||||
|
||||
## State-check cases
|
||||
|
||||
Per spec
|
||||
`sporel-meta/docs/superpowers/specs/2026-05-30-dep-fetcher-architecture.md` §5:
|
||||
|
||||
| Case | Condition | Action |
|
||||
|---|---|---|
|
||||
| A | `is_repo == false` | `git.clone(url, dir, v<X.Y.Z>)` |
|
||||
| B | `describe == v<X.Y.Z>` | No-op (clean) |
|
||||
| C | `describe` matches `v<X.Y.Z>-N-g…` or ends `-dirty` | Warning, no action |
|
||||
| D | `describe` is some other tag | Silent `git.checkout(dir, v<X.Y.Z>)` |
|
||||
| E | `describe` returns nil/err | Error |
|
||||
|
||||
## Gitea-slug derivation
|
||||
|
||||
Default: `sporel-<lib-id>`. Exception for libs whose ID ends in `.git`
|
||||
(currently only `lib-core.git`): the Gitea repo name pattern
|
||||
`*.git` is reserved by the protocol, so the slug appends `-lib`:
|
||||
|
||||
```
|
||||
lib-core.maps → sporel-lib-core.maps
|
||||
lib-management.input → sporel-lib-management.input
|
||||
lib-core.git → sporel-lib-core.git-lib (exception)
|
||||
```
|
||||
|
||||
This rule is encoded as a generic predicate (`_gitea_slug_for`) so any
|
||||
future `.git`-suffixed library uses the same translation without
|
||||
code change.
|
||||
|
||||
## Visibility model (slice 5 status)
|
||||
|
||||
Only the **Vagrant transitive closure** is public. Everything else is
|
||||
private (paid content, Steam-backed Gitea-SSO planned). Slice 5
|
||||
intentionally does not plumb credentials; if a private repo is in the
|
||||
closure and not locally present, the clone returns `errors[]` with
|
||||
`kind="net-fail"`. That is the documented limit of this slice.
|
||||
|
||||
## JSON parser
|
||||
|
||||
Bundled inline (see `init.lua::json_decode`). The engine has no
|
||||
generic `engine.json.decode` binding yet — `engine.asset.load_json` is
|
||||
sandboxed to the asset-tree and cannot read fixture/install paths.
|
||||
A future slice may extract this parser into a shared utility lib;
|
||||
until then it lives here.
|
||||
Reference in New Issue
Block a user