Skip to content

Commit b109afb

Browse files
committed
refactor(cli): Update RunApp usage to accept setup closures for lazy app initialization
1 parent d4285a1 commit b109afb

3 files changed

Lines changed: 216 additions & 135 deletions

File tree

docs/content/docs/cli/app-runner.mdx

Lines changed: 102 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,28 @@ import (
2222
)
2323

2424
func main() {
25-
app := forge.New(
26-
forge.WithAppName("my-app"),
27-
forge.WithAppVersion("1.0.0"),
28-
)
29-
30-
cli.RunApp(app)
25+
cli.RunApp(func(ctx cli.CommandContext) (forge.App, error) {
26+
return forge.New(
27+
forge.WithAppName("my-app"),
28+
forge.WithAppVersion("1.0.0"),
29+
), nil
30+
})
3131
}
3232
```
3333

34+
`RunApp` takes a setup closure instead of a pre-built app. The closure receives a `CommandContext` with access to parsed CLI flags, so you can use flag values to configure your app:
35+
36+
```go
37+
cli.RunApp(func(ctx cli.CommandContext) (forge.App, error) {
38+
return forge.New(
39+
forge.WithAppName("my-app"),
40+
forge.WithHTTPAddress(":"+ctx.String("port")),
41+
), nil
42+
},
43+
cli.WithGlobalFlags(cli.NewStringFlag("port", "p", "HTTP port", "8080")),
44+
)
45+
```
46+
3447
This gives you a fully functional CLI:
3548

3649
```bash
@@ -76,15 +89,33 @@ Configure `RunApp` behavior with functional options:
7689
Runs pending migrations before the HTTP server starts listening (during `PhaseBeforeRun`). Migrations run after all extensions are initialized but before the app accepts requests.
7790

7891
```go
79-
cli.RunApp(app, cli.WithAutoMigrate())
92+
cli.RunApp(appSetup, cli.WithAutoMigrate())
93+
```
94+
95+
### WithGlobalFlags
96+
97+
Add flags available to all commands and the setup closure. These flags are parsed before the setup closure runs, so you can use `ctx.String("flag")`, `ctx.Int("flag")`, etc. inside it:
98+
99+
```go
100+
cli.RunApp(func(ctx cli.CommandContext) (forge.App, error) {
101+
return forge.New(
102+
forge.WithAppName("my-app"),
103+
forge.WithHTTPAddress(":"+ctx.String("port")),
104+
), nil
105+
},
106+
cli.WithGlobalFlags(
107+
cli.NewStringFlag("port", "p", "HTTP port", "8080"),
108+
cli.NewStringFlag("env", "e", "Environment", "development"),
109+
),
110+
)
80111
```
81112

82113
### WithExtraCommands
83114

84115
Add custom commands alongside the built-in ones:
85116

86117
```go
87-
cli.RunApp(app,
118+
cli.RunApp(appSetup,
88119
cli.WithExtraCommands(
89120
cli.NewCommand("seed", "Seed the database", handleSeed),
90121
cli.NewCommand("reindex", "Rebuild search index", handleReindex),
@@ -97,23 +128,23 @@ cli.RunApp(app,
97128
Disable auto-registration of `migrate` commands even when `MigratableExtension` extensions are present:
98129

99130
```go
100-
cli.RunApp(app, cli.WithDisableMigrationCommands())
131+
cli.RunApp(appSetup, cli.WithDisableMigrationCommands())
101132
```
102133

103134
### WithDisableServeCommand
104135

105136
Disable the built-in `serve` command (useful for CLI-only tools):
106137

107138
```go
108-
cli.RunApp(app, cli.WithDisableServeCommand())
139+
cli.RunApp(appSetup, cli.WithDisableServeCommand())
109140
```
110141

111142
### WithCLIName / WithCLIVersion / WithCLIDescription
112143

113144
Override the CLI metadata (defaults to the Forge app's name and version):
114145

115146
```go
116-
cli.RunApp(app,
147+
cli.RunApp(appSetup,
117148
cli.WithCLIName("myctl"),
118149
cli.WithCLIVersion("2.0.0"),
119150
cli.WithCLIDescription("MyApp management CLI"),
@@ -131,13 +162,14 @@ When `WithAutoMigrate()` is enabled, the serve command registers a `PhaseBeforeR
131162

132163
```go
133164
func main() {
134-
app := forge.New(
135-
forge.WithAppName("my-app"),
136-
forge.WithExtensions(groveExt),
165+
cli.RunApp(func(ctx cli.CommandContext) (forge.App, error) {
166+
return forge.New(
167+
forge.WithAppName("my-app"),
168+
forge.WithExtensions(groveExt),
169+
), nil
170+
},
171+
cli.WithAutoMigrate(),
137172
)
138-
139-
// Migrations run automatically before HTTP server starts
140-
cli.RunApp(app, cli.WithAutoMigrate())
141173
}
142174
```
143175

@@ -213,6 +245,44 @@ grove Migrations (grove v0.12.0):
213245
└────────────────┴──────────────────┴─────────┴──────────────────────┘
214246
```
215247

248+
## Builder Pattern: NewAppRunner
249+
250+
For more complex setups, use `NewAppRunner` with method chaining:
251+
252+
```go
253+
func main() {
254+
cli.NewAppRunner(func(ctx cli.CommandContext) (forge.App, error) {
255+
return forge.New(
256+
forge.WithAppName("my-app"),
257+
forge.WithHTTPAddress(":"+ctx.String("port")),
258+
), nil
259+
}).
260+
Name("my-app").
261+
Version("1.0.0").
262+
AutoMigrate().
263+
WithGlobalFlags(cli.NewStringFlag("port", "p", "HTTP port", "8080")).
264+
WithExtraCommands(
265+
cli.NewCommand("seed", "Seed test data", handleSeed),
266+
).
267+
Run()
268+
}
269+
```
270+
271+
`NewAppRunner` returns an `*AppRunner` with the following methods:
272+
273+
| Method | Description |
274+
|--------|-------------|
275+
| `Name(string)` | Set CLI application name |
276+
| `Version(string)` | Set CLI application version |
277+
| `Description(string)` | Set CLI application description |
278+
| `AutoMigrate()` | Run migrations before serve |
279+
| `WithGlobalFlags(...Flag)` | Add flags available to all commands |
280+
| `WithExtraCommands(...Command)` | Add custom commands |
281+
| `DisableMigrationCommands()` | Hide migrate commands |
282+
| `DisableServeCommand()` | Hide serve command |
283+
| `Run()` | Build and execute the CLI (exits process) |
284+
| `Execute()` | Build and execute, returning errors (useful for testing) |
285+
216286
## Complete Example
217287

218288
```go
@@ -230,19 +300,22 @@ import (
230300
)
231301

232302
func main() {
233-
grove := groveext.New(
234-
groveext.WithDSN("postgres://localhost:5432/myapp"),
235-
groveext.WithMigrations(core.Migrations, billing.Migrations),
236-
)
237-
238-
app := forge.New(
239-
forge.WithAppName("myapp"),
240-
forge.WithAppVersion("1.0.0"),
241-
forge.WithExtensions(grove),
242-
)
243-
244-
cli.RunApp(app,
303+
cli.RunApp(func(ctx cli.CommandContext) (forge.App, error) {
304+
grove := groveext.New(
305+
groveext.WithDSN(ctx.String("dsn")),
306+
groveext.WithMigrations(core.Migrations, billing.Migrations),
307+
)
308+
309+
return forge.New(
310+
forge.WithAppName("myapp"),
311+
forge.WithAppVersion("1.0.0"),
312+
forge.WithExtensions(grove),
313+
), nil
314+
},
245315
cli.WithAutoMigrate(),
316+
cli.WithGlobalFlags(
317+
cli.NewStringFlag("dsn", "d", "Database connection string", "postgres://localhost:5432/myapp"),
318+
),
246319
cli.WithExtraCommands(
247320
cli.NewCommand("seed", "Seed test data", handleSeed),
248321
),

docs/content/docs/cli/forge-integration.mdx

Lines changed: 42 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -26,85 +26,68 @@ For most applications, use `cli.RunApp()` for zero-boilerplate CLI wrapping with
2626

2727
## Basic Integration
2828

29-
### Setting Up Forge CLI
29+
### Using RunApp (Recommended)
30+
31+
For most applications, `cli.RunApp()` is the simplest way to integrate:
32+
33+
```go
34+
package main
35+
36+
import (
37+
"github.com/xraph/forge"
38+
"github.com/xraph/forge/cli"
39+
)
40+
41+
func main() {
42+
cli.RunApp(func(ctx cli.CommandContext) (forge.App, error) {
43+
return forge.New(
44+
forge.WithAppName("myapp"),
45+
forge.WithAppVersion("1.0.0"),
46+
), nil
47+
},
48+
cli.WithAutoMigrate(),
49+
)
50+
}
51+
```
52+
53+
### Manual CLI Setup
54+
55+
For full control, create a CLI instance directly with `cli.New()`:
3056

3157
```go
3258
package main
3359

3460
import (
35-
"context"
3661
"fmt"
3762
"os"
38-
39-
"github.com/xraph/forge/cli"
63+
4064
"github.com/xraph/forge"
41-
"github.com/xraph/forge/config"
42-
"github.com/xraph/forge/extensions/database"
43-
"github.com/xraph/forge/logger"
65+
"github.com/xraph/forge/cli"
4466
)
4567

4668
func main() {
47-
// Initialize Forge application
48-
app := forge.New(forge.Config{
49-
Name: "myapp",
50-
Version: "1.0.0",
51-
})
52-
53-
// Initialize CLI with Forge integration
69+
app := forge.New(
70+
forge.WithAppName("myapp"),
71+
forge.WithAppVersion("1.0.0"),
72+
)
73+
74+
// Initialize CLI with Forge app integration
5475
cliApp := cli.New(cli.Config{
5576
Name: "myapp-cli",
5677
Description: "Command-line interface for MyApp",
5778
Version: "1.0.0",
58-
ForgeApp: app, // Enable Forge integration
79+
App: app, // Enable Forge integration
5980
})
60-
61-
// Add commands
81+
82+
// Add commands using NewCommand(name, description, handler)
6283
cliApp.AddCommand(
63-
cli.NewCommand("users").
64-
WithDescription("User management commands").
65-
WithSubcommands(
66-
cli.NewCommand("list").
67-
WithDescription("List all users").
68-
WithHandler(listUsersHandler),
69-
70-
cli.NewCommand("create").
71-
WithDescription("Create a new user").
72-
WithHandler(createUserHandler),
73-
74-
cli.NewCommand("delete").
75-
WithDescription("Delete a user").
76-
WithArgs(cli.Arg{
77-
Name: "id",
78-
Description: "User ID",
79-
Required: true,
80-
}).
81-
WithHandler(deleteUserHandler),
82-
),
84+
cli.NewCommand("users:list", "List all users", listUsersHandler),
8385
)
84-
8586
cliApp.AddCommand(
86-
cli.NewCommand("migrate").
87-
WithDescription("Database migration commands").
88-
WithSubcommands(
89-
cli.NewCommand("up").
90-
WithDescription("Run pending migrations").
91-
WithHandler(migrateUpHandler),
92-
93-
cli.NewCommand("down").
94-
WithDescription("Rollback migrations").
95-
WithFlags(
96-
cli.IntFlag("steps", "Number of migrations to rollback").
97-
WithDefault(1),
98-
).
99-
WithHandler(migrateDownHandler),
100-
101-
cli.NewCommand("status").
102-
WithDescription("Show migration status").
103-
WithHandler(migrateStatusHandler),
104-
),
87+
cli.NewCommand("users:create", "Create a new user", createUserHandler),
10588
)
106-
107-
if err := cliApp.Run(); err != nil {
89+
90+
if err := cliApp.Run(os.Args); err != nil {
10891
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
10992
os.Exit(1)
11093
}

0 commit comments

Comments
 (0)