From 10e06963d25963387998ec9935c489951bcbc10e Mon Sep 17 00:00:00 2001 From: TJ Kolleh Date: Sun, 5 Apr 2026 17:27:16 -0400 Subject: [PATCH 1/3] fix(ci): use lx exec to resolve luastatic build dependency This resolves the 'luastatic: command not found' error by allowing Lux to automatically resolve the local executable instead of modifying the system PATH. --- .github/workflows/release-please.yml | 4 ---- justfile | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 26969eb..bd798e3 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -46,10 +46,6 @@ jobs: sudo mv /tmp/just/just /usr/local/bin/ rm -rf /tmp/just - - name: Add Lux bin to PATH - if: ${{ steps.release.outputs.release_created }} - run: echo "$HOME/.lux/5.4/bin" >> "$GITHUB_PATH" - - name: Build Executable if: ${{ steps.release.outputs.release_created }} env: diff --git a/justfile b/justfile index 1ebafd5..5607d8e 100644 --- a/justfile +++ b/justfile @@ -153,7 +153,7 @@ build-system: prep [private] compile: @echo "Compiling standalone binary..." - cd lua && luastatic ../bin/spin.lua \ + cd lua && lx --lua-version {{ lua_version }} exec -- luastatic ../bin/spin.lua \ roda/init.lua roda/spinners.lua roda/ansi.lua roda/symbols.lua roda/util.lua roda/argp.lua \ ../{{ build_dir / 'libluv.a' }} ../{{ build_dir / 'libuv.a' }} ../{{ build_dir / 'libsystem.a' }} {{ lua_lib }} \ -I{{ lua_include }} && \ From 4fed8fff2b2676cb887687ff8fa91211399a2d5a Mon Sep 17 00:00:00 2001 From: TJ Kolleh Date: Wed, 8 Apr 2026 20:41:20 -0400 Subject: [PATCH 2/3] fix(build): add missing CLI entry point and upgrade Lux to v0.28.0 - Create bin/spin.lua with CLI argument parsing using roda.argp - Fix absolute path handling in justfile compile recipe - Upgrade all GitHub Actions workflows from Lux 0.18.8/0.25.3 to 0.28.0 - Update AGENTS.md documentation to reflect new version - Add validation check for bin/spin.lua existence before compilation --- .github/workflows/publish.yml | 7 +- .github/workflows/release-please.yml | 3 +- .github/workflows/tests.yml | 12 +-- AGENTS.md | 77 ++++++++++++++ bin/spin.lua | 154 +++++++++++++++++++++++++++ justfile | 5 +- 6 files changed, 245 insertions(+), 13 deletions(-) create mode 100644 AGENTS.md create mode 100644 bin/spin.lua diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 21fec6e..98e87d3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -27,10 +27,9 @@ jobs: - name: Install Lux uses: lumen-oss/gh-actions-lux@v1 with: - # Using v0.25.3 for publish (not v0.18.8 used in tests) - # Reason: v0.18.8 has bugs with lx upload causing 400 errors - # The publish workflow doesn't run tests, so no busted/penlight issues - version: 0.25.3 + # Using v0.28.0 for publish (consistent with all workflows) + # The busted/penlight dependency issues are resolved in v0.28.0 + version: 0.28.0 - name: Install LuaRocks (for validation) run: | diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index bd798e3..8d3e0cd 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -35,7 +35,8 @@ jobs: if: ${{ steps.release.outputs.release_created }} uses: lumen-oss/gh-actions-lux@v1 with: - version: 0.25.3 + # Using v0.28.0 for consistency with all workflows (busted/penlight issues resolved) + version: 0.28.0 - name: Install just if: ${{ steps.release.outputs.release_created }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bfef524..8de3246 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,11 +17,11 @@ jobs: - name: Install Lux uses: lumen-oss/gh-actions-lux@v1 with: - # Pinned to v0.18.8 due to busted/penlight transitive dependency issues + # Using v0.28.0 (busted/penlight dependency issues resolved in lux-lib 0.8.0+) + # Previously pinned to v0.18.8 due to busted/penlight transitive dependency issues # in newer versions (see lumen-oss/lux#722) - # The publish workflow uses v0.25.3 which doesn't have this issue - # because it doesn't run tests (no busted dependency) - version: 0.18.8 + # All workflows now use v0.28.0 consistently + version: 0.28.0 - name: Install system dependencies run: | @@ -49,8 +49,8 @@ jobs: - name: Install Lux uses: lumen-oss/gh-actions-lux@v1 with: - # See comment in test job above for version pinning rationale - version: 0.18.8 + # See comment in test job above for version upgrade rationale + version: 0.28.0 - name: Install just run: | diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b22cbd3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,77 @@ +# AGENTS.md + +## Project Overview +Roda (Portuguese for "wheel") is a pure Lua terminal spinner library and CLI tool for adding elegant loading indicators to bash scripts and Lua applications. It provides multiple spinner styles, colorized output, success/failure/warning states, and asynchronous command execution. The project includes a standalone executable built with static linking of luv and luasystem. + +## Repo Map +- `lua/roda/` – Core Lua library modules (`init.lua`, `ansi.lua`, `spinners.lua`, etc.) +- `spec/` – Busted unit tests (`*_spec.lua`) +- `demo/` – Example scripts and recordings +- `docs/` – Documentation +- `assets/` – Images and demo GIF +- `bin/` – CLI entry point (`spin.lua`) +- `.github/workflows/` – CI/CD pipelines (tests, publish, release) +- `justfile` – Primary task runner (format, lint, test, build) +- `.luacheckrc` – Linting configuration +- `lux.toml` – Dependency management (luasystem, luv, busted) + +## Setup +- **Prerequisites:** Lua (5.1+), Lux (package manager), just (task runner), git, C compiler (for static builds) +- **Install dependencies:** `just install` (uses Lux to fetch luasystem, luv, busted) +- **Environment variables (optional):** `LUA_PREFIX` (path to Lua installation), `BUILD_DIR` (default `.build`), `LOG_LEVEL` + +## Common Commands +All commands are run via `just ` from the repository root. + +| Command | Purpose | +|---------|---------| +| `just fmt` | Format Lua files (lux fmt) | +| `just lint` | Lint Lua files (lux check) | +| `just check` | Run both lint and format checks | +| `just test-unit` | Run unit tests (via lux/busted) | +| `just test` | Alias for `test-unit` | +| `just test-cli` | Build the standalone binary and run CLI integration tests | +| `just test-all` | Run unit + CLI tests | +| `just test-perf` | Performance benchmark with hyperfine | +| `just build` | Build standalone executable (static compile luv + luasystem) | +| `just install` | Install lux dependencies | +| `just dev ` | Run spinner directly without building (`lx lua -- bin/spin.lua`) | +| `just validate` | Full pre‑commit validation (check + test) | +| `just all` | Full CI pipeline (validate + build + integration tests) | +| `just clean` | Remove build artifacts and binaries | +| `just publish` | Publish to LuaRocks (requires confirmation) | + +## Coding Conventions +- **Formatting:** Use `just fmt` (lux fmt). No manual formatting needed. +- **Linting:** Follow `.luacheckrc` rules. Warnings are treated as errors except for ignored codes (deep nesting, long lines >120 chars, etc.). +- **Global variables:** Only allowed globals are listed in `.luacheckrc`. Avoid adding new ones. +- **Unused arguments:** Prefix with `_` (e.g., `_unused`) to suppress warnings. +- **Line length:** Maximum 120 characters. +- **Testing:** Write Busted specs in `spec/` following existing patterns. Use `describe`, `it`, `before_each`. +- **Error handling:** Use Lua `error` and `assert` appropriately; library functions return `nil, err` on failure. +- **Documentation:** Update README.md, CHANGELOG.md, and API reference in docs as needed. + +## Change Workflow +1. **Make your change** – Ensure changes are focused and small. +2. **Run `just check`** – Verify linting and formatting. +3. **Run `just test-unit`** – Ensure existing tests pass. +4. **Add/update tests** – Modify or create `*_spec.lua` files in `spec/`. +5. **Test CLI changes** – If modifying CLI behavior, run `just test-cli`. +6. **Build verification** – For changes to core library, run `just build` and test the binary. +7. **Keep diffs small** – Avoid large, unrelated changes in a single commit. +8. **Never commit secrets or credentials** – No secrets in the repository. + +## Gotchas +- **Lua version:** Default development uses Lua 5.5; CI runs tests with Lua 5.4. Ensure compatibility across 5.1+. +- **Static build dependencies:** Building the standalone binary (`just build`) clones luv and luasystem repos, compiles them statically, and links with liblua.a. Requires CMake, GCC/AR, and Lua development headers. +- **Lux version:** CI uses Lux 0.28.0 (busted dependency issues resolved); local development uses newer versions. Be aware of version mismatches. +- **Environment variables:** `LUA_PREFIX` may need to be set if Lua is installed in a non‑standard location (e.g., Homebrew on macOS). +- **Performance overhead:** The CLI adds minimal overhead; benchmark with `just test-perf` to ensure it stays under 1.3s for a 1s sleep. +- **Pre‑commit hooks:** The repo uses lefthook (see `lefthook.yml`); ensure hooks pass before committing. + +## When You're Stuck +- **Ask 1 targeted question** instead of guessing or exploring blindly. +- **If commands fail** due to environment/tooling, report the exact error and **STOP** rather than looping. +- **Prefer the smallest reproducible check** that validates the change (e.g., `just test-unit` for library changes, `just test-cli` for CLI changes). +- **When uncertain about architectural decisions**, ask before implementing. +- **Check the justfile** for the exact command you need; it's the source of truth for workflows. \ No newline at end of file diff --git a/bin/spin.lua b/bin/spin.lua new file mode 100644 index 0000000..ea2fd23 --- /dev/null +++ b/bin/spin.lua @@ -0,0 +1,154 @@ +#!/usr/bin/env -S lx lua +--- roda CLI entry point +-- Parses command-line arguments and runs a spinner for the given command. + +-- Add local path for development +package.path = "./lua/?.lua;./lua/?/init.lua;" .. package.path +package.cpath = "./.build/?.so;" .. package.cpath + +local argp = require("roda.argp") +local roda = require("roda") + +local parser = argp:new({ + name = "roda", + description = "Elegant terminal spinners for Lua. Wraps commands with visual feedback.", + epilog = "If no command is provided, roda will exit with status 0.", +}) + +parser:options({ + { + short = "t", + long = "title", + description = "Text to display next to the spinner", + type = "string", + count_params = 1, + dest = "title", + }, + { + long = "spinner", + description = "Spinner style to use (e.g., dots, line, arc)", + type = "string", + count_params = 1, + dest = "spinner", + }, + { + long = "show-output", + description = "Display the command's stdout/stderr after it finishes", + type = "boolean", + count_params = 0, + dest = "show_output", + }, + { + short = "c", + long = "color", + description = "Color of the spinner (e.g., cyan, green, yellow)", + type = "string", + count_params = 1, + dest = "color", + }, + { + long = "prefix-text", + description = "Text before spinner", + type = "string", + count_params = 1, + dest = "prefix_text", + }, + { + long = "suffix-text", + description = "Text after spinner text", + type = "string", + count_params = 1, + dest = "suffix_text", + }, + { + short = "h", + long = "help", + description = "Show this help message", + type = "boolean", + count_params = 0, + dest = "help", + }, +}) + +local function main(...) + local args = { ... } + if #args == 0 then + -- No arguments, exit 0 as per test expectation + os.exit(0) + end + + local parsed, err = pcall(function() + return parser:parse(args) + end) + + if not parsed then + io.stderr:write("error: " .. tostring(err) .. "\n") + os.exit(1) + end + + local options = err -- result from pcall + + if options.help then + parser:print_system_help() + os.exit(0) + end + + -- Find the '--' separator + local separator_index = nil + for i, arg in ipairs(args) do + if arg == "--" then + separator_index = i + break + end + end + + local command_args = {} + if separator_index then + for i = separator_index + 1, #args do + table.insert(command_args, args[i]) + end + end + + -- Build spinner options + local spinner_opts = {} + if options.title then + spinner_opts.text = options.title + end + if options.spinner then + spinner_opts.spinner = options.spinner + end + if options.color then + spinner_opts.color = options.color + end + if options.prefix_text then + spinner_opts.prefixText = options.prefix_text + end + if options.suffix_text then + spinner_opts.suffixText = options.suffix_text + end + + local spinner = roda(spinner_opts) + + if #command_args == 0 then + -- No command to execute, just exit (test 6) + os.exit(0) + end + + local command = command_args[1] + local cmd_args = {} + for i = 2, #command_args do + table.insert(cmd_args, command_args[i]) + end + + spinner:execute(command, cmd_args)(function(exit_code, output) + if options.show_output and output and #output > 0 then + io.stdout:write(output) + io.stdout:flush() + end + os.exit(exit_code) + end) + + roda.run() +end + +main(...) \ No newline at end of file diff --git a/justfile b/justfile index bab65ce..8fac6c6 100644 --- a/justfile +++ b/justfile @@ -155,12 +155,13 @@ build-system: prep [group('build')] [private] compile: + @echo {{ assert(path_exists("bin/spin.lua") == "true", "bin/spin.lua not found - CLI entry point missing") }} @echo "Compiling standalone binary..." cd lua && lx --lua-version {{ lua_version }} exec -- luastatic ../bin/spin.lua \ roda/init.lua roda/spinners.lua roda/ansi.lua roda/symbols.lua roda/util.lua roda/argp.lua \ - ../{{ build_dir / 'libluv.a' }} ../{{ build_dir / 'libuv.a' }} ../{{ build_dir / 'libsystem.a' }} {{ lua_lib }} \ + {{ build_dir / 'libluv.a' }} {{ build_dir / 'libuv.a' }} {{ build_dir / 'libsystem.a' }} {{ lua_lib }} \ -I{{ lua_include }} && \ - mv spin.luastatic.c ../{{ build_dir }}/ && \ + mv spin.luastatic.c {{ build_dir }}/ && \ cd .. && \ mv lua/spin roda From 69cf9e67afd8526a44abb8b5d7290b9fe6e47c6b Mon Sep 17 00:00:00 2001 From: TJ Kolleh Date: Wed, 8 Apr 2026 21:56:38 -0400 Subject: [PATCH 3/3] fix: use proper value for lx command --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f444079..8a3a314 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -60,6 +60,6 @@ jobs: # Sole LuaRocks publication point - uses lx upload which has built-in JSON # handling and doesn't require system Lua JSON libraries - name: Upload to LuaRocks - run: lx --lua-version 5.1 upload --verbose + run: lx --verbose --lua-version 5.1 upload env: LUX_API_KEY: ${{ secrets.LUX_API_KEY }}