lde is a package manager and toolkit for Lua, written in Lua and running on LuaJIT. It manages project-local dependencies, runs Lua programs, and compiles them into single executables.
packages/
lde/ # The CLI binary itself (entry: src/init.lua)
lde-core/ # Core library: Package, Lockfile, runtime, install logic
lde-test/ # Built-in test framework
ansi/clap/env/fs/git/http/json/path/process/semver/util/ # Internal packages
sea/ # Single-executable assembly (compiles bundles into binaries)
archive/ # Archive extraction support
luarocks/ # LuaRocks integration
rocked/ # Rockspec support
schemas/ # JSON schema for lde.json
tests/ # Top-level integration test fixtures (e.g. some-package/)
When lde run (or lde test) executes a script, it sets package.path and package.cpath to point at the package's target/ directory:
target/?.lua
target/?/init.lua
target/?.so (or .dll / .dylib)
target/ is populated by lde install / lde run (which calls installDependencies first). Each dependency is installed as a symlink (or copy if it has a build script) at target/<alias>. So require("json") resolves to target/json/init.lua, which is a symlink to packages/json/src/init.lua.
The key: the require name is the key in lde.json dependencies, not the package's name field. You can alias a package by using a different key.
During lde test, the runner automatically exposes the package's tests/ directory as target/tests (symlinked or copied). This means test files can require("tests.lib.something") to share helpers across test files.
Example from packages/lde/tests/main.test.lua:
local ldecli = require("tests.lib.ldecli")This resolves to tests/lib/ldecli.lua via the target/tests symlink.
Test files must match **/*.test.lua. See packages/lde-test for the full API.
local test = require("lde-test")
test.it("does something", function()
test.equal(a, b) -- assertions take no message argument
test.truthy(x)
test.deepEqual(t1, t2)
end)Run tests: lde test (from a package dir, or from repo root to run all packages).
Lockfile is lde.lock. The target/ directory is the build output — never commit it.
lde run/lde testboth callpkg:build()+pkg:installDependencies()automatically.- Build script: if
build.luaexists at the package root, it's executed withLDE_OUTPUT_DIRset to the output path. Otherwise,src/is symlinked directly intotarget/<name>. target/.installedstores an FNV1a hash oflde.lockas a fast-path cache — if it matches, install is skipped entirely.
After making changes to any package source, rebuild the binary:
cd packages/lde
lde compileThis outputs packages/lde/lde (or lde.exe on Windows). To install it, copy it to ~/.lde/lde.
Important: Tests in packages/lde/tests/ run the actual lde CLI binary via env.execPath(). If those tests are failing after source changes, you need to recompile and replace the binary first.
lde run / lde test execute scripts in an isolated environment:
package.loadedis cleared of non-builtins before and restored after each run.- A fresh
_Gmetatable is created per execution (setfenv). ffi.cdefis patched to silently ignore "attempt to redefine" errors (safe for repeated runs in the same process).package.path/cpathare restored after execution.
This means multiple lde run calls in the same process don't pollute each other's module state.
| Package | Purpose |
|---|---|
lde-core |
Package, Lockfile, install/build/run/test/compile logic |
lde-test |
Test framework (require("lde-test") in test files) |
clap |
CLI argument parsing (args:option(), args:flag(), args:pop()) |
ansi |
Colored terminal output (ansi.printf("{red}msg")) |
fs |
Filesystem ops (read, write, mkdir, mklink, scan, stat) |
process |
Process execution (process.exec(bin, args, opts)) |
sea |
Compiles a bundled Lua string + native libs into a self-contained binary |
env |
Env vars, cwd, env.execPath() |
- All packages are in
packages/and depend on each other via{ "path": "../<pkg>" }. - The
ldepackage'slde.jsonlists all sibling packages as path dependencies.
lde can be built with BOOTSTRAP=1 using stock LuaJIT (no existing lde binary required). In this mode, packages/lde/src/init.lua manually creates symlinks in target/ for all dependencies instead of using the normal install flow.
The project was previously named lpm. You may see lpm.json or lpm-test references in older code — always use the lde equivalents (lde.json, lde-test) when writing new code.
{ "name": "my-package", "version": "0.1.0", "bin": "src/main.lua", // optional, overrides default entry (target/<name>/init.lua) "engine": "lde", // "lde" (default), "lua", or "luajit" "scripts": { "build": "..." }, // runnable via `lde <name>` or `lde run <name>` "dependencies": { "json": { "path": "../json" }, // local path "hood": { "git": "https://...", "commit": "abc123" }, "semver": { "version": "1.0.0" }, // registry "mylib": { "luarocks": "luafilesystem" }, // luarocks "archive": { "archive": "https://.../x.zip" }, "winapi": { "git": "...", "optional": true } }, "devDependencies": { ... }, "features": { "windows": ["winapi"] // optional deps enabled per platform/feature flag } }