Files
sporel-lib-management.dep-f…/README.md
Axel Meyer 1cab8610b1 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.
2026-05-31 01:46:49 +02:00

92 lines
3.4 KiB
Markdown

# 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.