Skip to content

Commit 166706d

Browse files
committed
feat: agents file generation for users if cli is present
1 parent 40da031 commit 166706d

1 file changed

Lines changed: 205 additions & 0 deletions

File tree

‎packages/lde-core/src/package/initialize.lua‎

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,210 @@ local fs = require("fs")
33
local util = require("util")
44
local ansi = require("ansi")
55
local git2 = require("git2-sys")
6+
local process = require("process")
67

78
local Package = require("lde-core.package")
89

910
local function hasGit()
1011
return true
1112
end
1213

14+
---@type string
15+
local AGENT_TEMPLATE = util.dedent([[
16+
# Building Projects with lde
17+
18+
`lde` is a package manager and toolkit for Lua (LuaJIT). It manages project-local dependencies, runs Lua programs, runs tests, and compiles projects into single executables.
19+
20+
## Quick Reference
21+
22+
```sh
23+
lde run # build + install deps + run entry point (src/init.lua)
24+
lde test # run all **/*.test.lua files
25+
lde compile # compile to a single native executable
26+
lde bundle # bundle into a single .lua file
27+
lde -e "<code>" # run a one-liner with project deps available
28+
lde ./path/to/file.lua # run an arbitrary file with project deps available
29+
30+
lde add <alias> --path ../pkg # add a local path dependency
31+
lde add <alias> --git <url> # add a git dependency (commit auto-pinned)
32+
lde add <alias>@<version> # add a registry dependency
33+
lde remove <alias> # remove a dependency
34+
```
35+
36+
**Always use `lde add`/`lde remove`** — never edit `lde.json` by hand or the lockfile goes out of sync.
37+
38+
Dependency installation happens automatically on `lde run`, `lde test`, and `lde compile`. If an external runtime (e.g. LOVE2D) runs your code, use `lde sync` to populate `target/`.
39+
40+
## Project Structure
41+
42+
```
43+
├── lde.json # package manifest — dependencies, scripts, metadata
44+
├── lde.lock # lockfile — commit this
45+
├── src/
46+
│ └── init.lua # default entry point
47+
├── tests/ # test files (**/*.test.lua)
48+
├── build.lua # (optional) custom build script
49+
└── target/ # build output — NEVER commit this
50+
```
51+
52+
## The Manifest (`lde.json`)
53+
54+
```jsonc
55+
{
56+
"name": "my-package",
57+
"version": "0.1.0",
58+
"bin": "src/main.lua", // optional entry point override (default: src/init.lua)
59+
"engine": "lde", // "lde" (default), "lua", or "luajit"
60+
"scripts": {
61+
"build": "echo building..." // runnable via `lde run build`
62+
},
63+
"dependencies": {
64+
"json": { "path": "../json" }, // local path
65+
"hood": { "git": "https://github.com/user/hood" }, // git
66+
"semver": { "version": "1.0.0" }, // registry
67+
"mylib": { "luarocks": "luafilesystem" }, // LuaRocks
68+
"winapi": { "git": "...", "optional": true } // optional (feature-gated)
69+
},
70+
"devDependencies": {
71+
"lde-test": { "version": "1.0.0" }
72+
},
73+
"features": {
74+
"windows": ["winapi"]
75+
}
76+
}
77+
```
78+
79+
Platform features (`windows`, `linux`, `macos`, `android`) are auto-detected. Optional deps listed under the current platform are installed automatically.
80+
81+
`lde-test` and `lde-build` are included with the lde runtime — adding them to `devDependencies` is only needed for LSP typings.
82+
83+
## How `require()` Resolution Works
84+
85+
`lde run` and `lde test` set `package.path` / `package.cpath` to point at `target/`:
86+
87+
```
88+
target/?.lua
89+
target/?/init.lua
90+
target/?.so (or .dll / .dylib)
91+
```
92+
93+
Dependencies are installed as symlinks at `target/<alias>`. **The require name is the key in `dependencies`, not the package's `name` field.** This means you can alias packages by choosing a different key.
94+
95+
During `lde test`, the project's `tests/` directory is exposed as `target/tests`, so test files can share helpers:
96+
97+
```lua
98+
-- tests/foo.test.lua
99+
local helper = require("tests.lib.helper") -- resolves to tests/lib/helper.lua
100+
```
101+
102+
## Testing
103+
104+
```sh
105+
lde test # run all **/*.test.lua in the package
106+
lde test -- path/to/test # run a specific test file
107+
```
108+
109+
Test files must match `**/*.test.lua`:
110+
111+
```lua
112+
local test = require("lde-test")
113+
114+
test.it("describes the test", function()
115+
test.equal(actual, expected)
116+
test.notEqual(a, b)
117+
test.truthy(x)
118+
test.falsy(x)
119+
test.deepEqual(t1, t2) -- recursive deep equality (including metatables)
120+
test.match(actual, expected) -- subset match (like jest toMatchObject)
121+
test.includes(haystack, needle) -- string contains substring
122+
test.greater(a, b) -- a > b
123+
test.less(a, b) -- a < b
124+
test.greaterEqual(a, b) -- a >= b
125+
test.lessEqual(a, b) -- a <= b
126+
test.count(tbl) -- returns number of keys (via pairs)
127+
end)
128+
129+
test.skip("pending test", function() ... end)
130+
test.skipIf(condition)("conditionally skipped", function() ... end)
131+
test.afterEach(function() ... end) -- runs after each test
132+
test.afterAll(function() ... end) -- runs once after all tests
133+
```
134+
135+
Assertions take no message argument — the test name serves as the description.
136+
137+
## Compiling
138+
139+
```sh
140+
lde compile # compile to a single native executable
141+
lde bundle # bundle into a single .lua file (no embedded runtime)
142+
```
143+
144+
`lde compile` produces a self-contained binary that bundles your code, all dependencies, and the LuaJIT runtime.
145+
146+
## Build Scripts (`build.lua`)
147+
148+
If a package has a `build.lua` at its root, lde executes it instead of symlinking `src/`. **Always use the `lde-build` API** — it handles cross-platform behavior and provides fetch/extract without requiring any tools beyond lde on the user's machine.
149+
150+
```lua
151+
local build = require("lde-build")
152+
153+
-- Network (no curl/wget needed)
154+
local body = build:fetch(url) -- HTTP GET, returns body string
155+
156+
-- File I/O (all paths relative to output dir at target/<name>)
157+
build:write("filename.lua", content) -- write file
158+
local content = build:read("f") -- read file
159+
build:copy("src", "dst") -- copy file or directory
160+
build:move("src", "dst") -- move/rename
161+
build:delete("path") -- delete file or directory
162+
build:exists("path") -- returns bool
163+
build:extract("archive.zip", "dest/") -- extract zip/tar (no tar/unzip needed)
164+
165+
-- Shell
166+
build:sh("gcc -shared -o lib.o src.c") -- run command, asserts exit code 0
167+
```
168+
169+
The env var `LDE_OUTPUT_DIR` is also set to the output path.
170+
171+
## Useful Built-in Libraries
172+
173+
| Package | Purpose |
174+
|---------|---------|
175+
| `fs` | Filesystem operations (read, write, copy, move, mkdir, scan, stat) |
176+
| `path` | Path manipulation (join, basename, dirname, resolve, relative) |
177+
| `env` | Environment (get/set vars, cwd, executable path) |
178+
| `process` | Subprocess execution |
179+
| `json` | JSON encode/decode |
180+
| `ansi` | Colored terminal output |
181+
| `clap` | CLI argument parsing |
182+
183+
Add them with: `lde add <name> --git https://github.com/lde-org/<name>`
184+
185+
## Lockfile
186+
187+
`lde.lock` pins exact versions, commits, and hashes. **Always commit it.** `target/` is build output — **never commit it.**
188+
189+
## Troubleshooting
190+
191+
| Problem | Solution |
192+
|---------|----------|
193+
| `module 'x' not found` | Run `lde run` — it auto-installs deps. Verify the require name matches the key in `lde.json` `dependencies`. |
194+
| Stale dependencies / changes not taking effect | Delete `target/` and `lde.lock`, then re-run. |
195+
| Global cache may be corrupted | Delete `~/.lde/git` and/or `~/.lde/tar` to clear cached downloads. |
196+
| Build script failing | `build:sh()` asserts exit code 0 — wrap in `pcall` if failures are expected. |
197+
| Tests not found | Test files must match `**/*.test.lua`. They must be in the `tests/` directory. |
198+
]])
199+
200+
--- Check whether a binary exists on PATH
201+
---@param name string
202+
local function hasBinary(name)
203+
if process.platform == "win32" then
204+
return process.exec("where", { name }) == 0
205+
else
206+
return process.exec("command", { "-v", name }) == 0
207+
end
208+
end
209+
13210
---@param dir string
14211
local function isInsideGitRepo(dir)
15212
local current = dir
@@ -22,6 +219,7 @@ local function isInsideGitRepo(dir)
22219
if parent == current then break end
23220
current = parent
24221
end
222+
25223
return false
26224
end
27225

@@ -105,6 +303,13 @@ local function initPackage(dir)
105303
fs.write(path.join(src, "init.lua"), "print('Hello, world!')")
106304
end
107305

306+
-- Write agent instructions if a known coding agent is present
307+
if hasBinary("claude") then
308+
fs.write(path.join(dir, "CLAUDE.md"), AGENT_TEMPLATE)
309+
elseif hasBinary("opencode") then
310+
fs.write(path.join(dir, "AGENTS.md"), AGENT_TEMPLATE)
311+
end
312+
108313
return package
109314
end
110315

0 commit comments

Comments
 (0)