Implement sjnetpkg 0.1.0, a Git-based package manager
Packages are ordinary Git repositories carrying a sjnetpkg.toml, so sjnetpkg only needs to resolve, clone, track and run them instead of inventing an archive format or build system. This adds the v0.1 CLI (install from registry or Git URL, remove, list/inspect, registry update, run) on top of the Python standard library, together with a test suite. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
@@ -1,3 +1,170 @@
|
||||
# sjnetPKG
|
||||
|
||||
Package manager
|
||||
A simple, Git-based package manager.
|
||||
|
||||
A package is not a pre-compiled file or a special archive format - it is an
|
||||
ordinary Git repository. `sjnetpkg` finds packages, clones them, keeps track
|
||||
of what is installed, updates them and runs them.
|
||||
|
||||
```
|
||||
Git repository -> ~/.sjnetpkg/pkg/<name> -> sjnetpkg.toml -> run command
|
||||
```
|
||||
|
||||
The repository (including `.git`) is kept completely intact, and `sjnetpkg`
|
||||
never modifies the package code itself.
|
||||
|
||||
## Installation
|
||||
|
||||
Requires Python 3.11 or newer and the `git` executable on `PATH`.
|
||||
|
||||
```bash
|
||||
pip install .
|
||||
# or, for development
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
You can also run it straight from the checkout:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=src python3 -m sjnetpkg --help
|
||||
```
|
||||
|
||||
## Package format
|
||||
|
||||
Every package must contain a `sjnetpkg.toml` in its root directory:
|
||||
|
||||
```toml
|
||||
name = "cool-tool"
|
||||
version = "1.0.0"
|
||||
|
||||
[run]
|
||||
command = "cargo run"
|
||||
```
|
||||
|
||||
`name` and `version` are required. `version` is treated as an opaque string;
|
||||
v0.1 performs no semantic-version resolution.
|
||||
|
||||
The optional `[run]` section defines how the package is started. The command
|
||||
is language-agnostic, for example:
|
||||
|
||||
| Language | `command` |
|
||||
| -------- | --------------------- |
|
||||
| Rust | `cargo run` |
|
||||
| Python | `python main.py` |
|
||||
| Go | `go run .` |
|
||||
| Node.js | `npm start` |
|
||||
| Bash | `bash main.sh` |
|
||||
|
||||
The command string is passed to the platform shell (`/bin/sh` on POSIX,
|
||||
`cmd.exe` on Windows), so multi-argument commands such as
|
||||
`cargo run --release` work as written. The process runs with the package
|
||||
directory as its working directory, and STDIN, STDOUT and STDERR are passed
|
||||
through directly.
|
||||
|
||||
## Registry
|
||||
|
||||
The registry is a plain TOML file mapping package names to Git repositories:
|
||||
|
||||
```toml
|
||||
[packages.cool-tool]
|
||||
repo = "https://github.com/example/cool-tool.git"
|
||||
```
|
||||
|
||||
The registry URL is configured in `~/.sjnetpkg/config.toml`:
|
||||
|
||||
```toml
|
||||
registry = "https://pkg.serverjonas.de/registry.toml"
|
||||
```
|
||||
|
||||
A downloaded copy is kept at `~/.sjnetpkg/registry.toml` so packages can be
|
||||
resolved even when the remote registry is unreachable. A failed or invalid
|
||||
download never destroys the existing local copy.
|
||||
|
||||
The default registry is `https://pkg.serverjonas.de/registry.toml`, defined by
|
||||
`DEFAULT_REGISTRY_URL` in `src/sjnetpkg/config.py`. It is written into
|
||||
`config.toml` on first start; afterwards the file in the home directory wins.
|
||||
|
||||
## CLI
|
||||
|
||||
```
|
||||
Usage:
|
||||
sjnetpkg -S <package> install a package from the registry
|
||||
sjnetpkg -U <url> install a package directly from a Git URL
|
||||
sjnetpkg -R <package> remove an installed package
|
||||
sjnetpkg -Q list installed packages
|
||||
sjnetpkg -Q <package> show information about an installed package
|
||||
sjnetpkg -Sy update the local copy of the registry
|
||||
sjnetpkg run <package> run an installed package
|
||||
sjnetpkg --help show the help
|
||||
sjnetpkg --version show the version
|
||||
```
|
||||
|
||||
With `-U`, the directory name is derived from the repository name in the URL,
|
||||
while the `name` in `sjnetpkg.toml` is the final package identity. The two are
|
||||
allowed to differ.
|
||||
|
||||
Installation never starts a package automatically - use `sjnetpkg run <name>`.
|
||||
Packages are installed into `~/.sjnetpkg/pkg/` and are never copied to
|
||||
`/usr/bin`, `/usr/local/bin` or `~/.local/bin`.
|
||||
|
||||
Exit codes: `0` on success, `1` on errors, `2` on command line usage errors.
|
||||
Errors are reported as short messages, never as Python tracebacks.
|
||||
|
||||
## Directory layout
|
||||
|
||||
```
|
||||
~/.sjnetpkg/
|
||||
|-- config.toml
|
||||
|-- registry.toml
|
||||
`-- pkg/
|
||||
`-- cool-tool/
|
||||
|-- .git/
|
||||
|-- sjnetpkg.toml
|
||||
`-- ...
|
||||
```
|
||||
|
||||
The home directory can be overridden with the `SJNETPKG_HOME` environment
|
||||
variable (useful for testing and custom setups). It defaults to
|
||||
`pathlib.Path.home() / ".sjnetpkg"`.
|
||||
|
||||
## Updating packages
|
||||
|
||||
Updates run `git pull` inside a package directory. `sjnetpkg -Su`, which
|
||||
updates all installed packages, is planned; the update concept is defined but
|
||||
v0.1 does not implement it yet. An update will never start a package.
|
||||
|
||||
## Development
|
||||
|
||||
Modules and responsibilities:
|
||||
|
||||
* `cli.py` - arguments, options, command dispatch, exit codes, error messages
|
||||
* `config.py` - the `~/.sjnetpkg/` layout, `config.toml`, registry configuration
|
||||
* `registry.py` - downloading, reading and searching the registry
|
||||
* `package.py` - package paths, `sjnetpkg.toml`, metadata, install state
|
||||
* `git.py` - `git clone`, `git pull`, git availability and errors
|
||||
* `runner.py` - run command lookup, working directory, process, exit code
|
||||
|
||||
Run the tests (standard library `unittest`, no extra dependencies):
|
||||
|
||||
```bash
|
||||
PYTHONPATH=src python3 -m unittest discover -s tests -t tests
|
||||
```
|
||||
|
||||
They also run under pytest, which picks up `tests/conftest.py`.
|
||||
|
||||
## Scope of v0.1
|
||||
|
||||
Deliberately not included: dependency resolver, version constraints,
|
||||
automatic dependency installation, sandbox, signatures, checksums, binary
|
||||
packages, build system, automatic compiler installation, global executables,
|
||||
daemon, package hooks, an own Git implementation, remote package cache.
|
||||
|
||||
## Security
|
||||
|
||||
A package is potentially executable foreign code: its `[run] command` may
|
||||
start arbitrary commands. v0.1 provides no sandbox - only install and run
|
||||
packages you trust.
|
||||
|
||||
## License
|
||||
|
||||
To be determined.
|
||||
|
||||
Reference in New Issue
Block a user