fix: copy-on-wrap commands and typed errors

Clone commands before wrapping Before hooks, export ErrLifecycleNotInContext
and ErrNoDependencies, add Run helper, split command.go and deps.go.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 18:04:07 -07:00
parent 60ecc69620
commit f64f7fa04e
9 changed files with 282 additions and 100 deletions
+47 -1
View File
@@ -1,3 +1,49 @@
# appcli
appcli provides boilerplate dependency handling for command line apps based on [urfave/cli](https://github.com/urfave/cli) that utilize [app](https://gitea.auvem.com/go-toolkit/app).
Bridge between [urfave/cli/v3](https://github.com/urfave/cli/v3) and [app](https://gitea.auvem.com/go-toolkit/app) lifecycles.
## Install
```bash
go get gitea.auvem.com/go-toolkit/appcli
```
## Example
```go
func main() {
lc := app.NewLifecycle()
defer lc.Teardown()
root := appcli.NewRootCommand(&cli.Command{
Name: "myapp",
Usage: "My application",
Commands: []*cli.Command{
appcli.NewCommand(&cli.Command{
Name: "serve",
Usage: "Run the server",
Action: func(ctx context.Context, c *cli.Command) error {
// modules already loaded via DepFn
return nil
},
}, func(l *app.Lifecycle, c *cli.Command) ([]*app.Module, error) {
return []*app.Module{myModule}, nil
}),
},
})
if err := appcli.Run(context.Background(), lc, root, os.Args); err != nil {
log.Fatal(err)
}
}
```
## NewRootCommand vs NewCommand
- `NewRootCommand` adds global `--verbose` / `-v` (unless already defined)
- Both wrap `Before` to call `lifecycle.Require` with modules from `DepFn`
- Wrapped commands are copied; the original `*cli.Command` is not mutated
## DepFn
The `*cli.Command` argument is the command whose `Before` hook is running. For nested commands, parent hooks receive the parent command; use `VerboseFromCommand` to read root flags from subcommands.