Skip to content

Commit 4e05fb0

Browse files
feat: rebuild plugin on file changes while running dev server
1 parent 654bec7 commit 4e05fb0

File tree

7 files changed

+137
-25
lines changed

7 files changed

+137
-25
lines changed

cmd/cmd_run.go

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
package main
22

33
import (
4+
"errors"
5+
"log"
46
"os"
7+
"path/filepath"
58

69
"github.com/customrealms/cli/internal/build"
710
"github.com/customrealms/cli/internal/project"
811
"github.com/customrealms/cli/internal/serve"
912
"github.com/customrealms/cli/internal/server"
13+
"github.com/fsnotify/fsnotify"
14+
"golang.org/x/sync/errgroup"
1015
)
1116

1217
type RunCmd struct {
1318
ProjectDir string `name:"project" short:"p" usage:"plugin project directory" optional:""`
14-
McVersion string `name:"mc" short:"mc" usage:"Minecraft version number target" optional:""`
19+
McVersion string `name:"mc" usage:"Minecraft version number target" optional:""`
1520
TemplateJarFile string `name:"jar" short:"t" usage:"template JAR file" optional:""`
1621
}
1722

@@ -70,11 +75,77 @@ func (c *RunCmd) Run() error {
7075
return err
7176
}
7277

73-
// Create the serve runner
74-
serveAction := serve.ServeAction{
75-
MinecraftVersion: minecraftVersion,
76-
PluginJarPath: outputFile,
77-
ServerJarFetcher: serverJarFetcher,
78-
}
79-
return serveAction.Run(ctx)
78+
eg, ctx := errgroup.WithContext(ctx)
79+
80+
chanPluginUpdated := make(chan struct{})
81+
chanServerStopped := make(chan struct{})
82+
eg.Go(func() error {
83+
// Create new watcher.
84+
watcher, err := fsnotify.NewWatcher()
85+
if err != nil {
86+
return err
87+
}
88+
defer watcher.Close()
89+
90+
// Start listening for events.
91+
go func() {
92+
for {
93+
select {
94+
case event, ok := <-watcher.Events:
95+
if !ok {
96+
return
97+
}
98+
if event.Has(fsnotify.Write) {
99+
// Rebuild the plugin JAR file
100+
if err := buildAction.Run(ctx); err != nil {
101+
log.Println("Error: ", err)
102+
} else {
103+
select {
104+
case chanPluginUpdated <- struct{}{}:
105+
case <-ctx.Done():
106+
return
107+
}
108+
}
109+
}
110+
case err, ok := <-watcher.Errors:
111+
if !ok {
112+
return
113+
}
114+
log.Println("error:", err)
115+
case <-ctx.Done():
116+
return
117+
case <-chanServerStopped:
118+
return
119+
}
120+
}
121+
}()
122+
123+
// Add the project directory and src directory to the watcher
124+
if err := errors.Join(
125+
watcher.Add(c.ProjectDir),
126+
watcher.Add(filepath.Join(c.ProjectDir, "src")),
127+
); err != nil {
128+
return err
129+
}
130+
131+
// Block until the server is stopped
132+
select {
133+
case <-ctx.Done():
134+
return ctx.Err()
135+
case <-chanServerStopped:
136+
return nil
137+
}
138+
})
139+
eg.Go(func() error {
140+
defer close(chanServerStopped)
141+
142+
// Create the serve runner
143+
serveAction := serve.ServeAction{
144+
MinecraftVersion: minecraftVersion,
145+
PluginJarPath: outputFile,
146+
ServerJarFetcher: serverJarFetcher,
147+
}
148+
return serveAction.Run(ctx, chanPluginUpdated)
149+
})
150+
return eg.Wait()
80151
}

cmd/cmd_yml.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
type YmlCmd struct {
1212
ProjectDir string `name:"project" short:"p" usage:"plugin project directory" optional:""`
13-
McVersion string `name:"mc" short:"mc" usage:"Minecraft version number target" optional:""`
13+
McVersion string `name:"mc" usage:"Minecraft version number target" optional:""`
1414
}
1515

1616
func (c *YmlCmd) Run() error {

cmd/main.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ import (
1212
)
1313

1414
var cli struct {
15-
VersionCmd VersionCmd `cmd:"" help:"Show the version of the CLI."`
16-
InitCmd InitCmd `cmd:"" help:"Initialize a new plugin project."`
17-
BuildCmd BuildCmd `cmd:"" help:"Build the plugin JAR file."`
18-
RunCmd RunCmd `cmd:"" help:"Build and serve the plugin in a Minecraft server."`
19-
YmlCmd YmlCmd `cmd:"" help:"Generate the plugin.yml file."`
15+
VersionCmd VersionCmd `cmd:"" name:"version" help:"Show the version of the CLI."`
16+
InitCmd InitCmd `cmd:"" name:"init" help:"Initialize a new plugin project."`
17+
BuildCmd BuildCmd `cmd:"" name:"build" help:"Build the plugin JAR file."`
18+
RunCmd RunCmd `cmd:"" name:"run" help:"Build and serve the plugin in a Minecraft server."`
19+
YmlCmd YmlCmd `cmd:"" name:"yml" help:"Generate the plugin.yml file."`
2020
}
2121

2222
func rootContext() (context.Context, context.CancelFunc) {

go.mod

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,10 @@ module github.com/customrealms/cli
22

33
go 1.22
44

5-
require github.com/alecthomas/kong v0.9.0
5+
require (
6+
github.com/alecthomas/kong v0.9.0
7+
github.com/fsnotify/fsnotify v1.7.0
8+
golang.org/x/sync v0.7.0
9+
)
10+
11+
require golang.org/x/sys v0.4.0 // indirect

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,11 @@ github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA
44
github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os=
55
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
66
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
7+
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
8+
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
79
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
810
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
11+
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
12+
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
13+
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
14+
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

internal/minecraft/versions.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type paperMcBuild struct {
2424

2525
func LookupVersion(ctx context.Context, versionStr string) (Version, error) {
2626
// Lookup the version from PaperMC
27-
builds, err := downloadJSON[paperMcBuilds](ctx, fmt.Sprintf("https://papermc.io/api/v2/projects/paper/versions/%s", versionStr))
27+
builds, err := downloadJSON[paperMcBuilds](ctx, fmt.Sprintf("https://papermc.io/api/v2/projects/paper/versions/%s/builds", versionStr))
2828
if err != nil {
2929
return nil, fmt.Errorf("download builds list: %w", err)
3030
}
@@ -48,6 +48,7 @@ func LookupVersion(ctx context.Context, versionStr string) (Version, error) {
4848
}
4949

5050
func downloadJSON[T any](ctx context.Context, url string) (*T, error) {
51+
fmt.Println(url)
5152
// Create the HTTP request
5253
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
5354
if err != nil {

internal/serve/serve.go

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/customrealms/cli/internal/minecraft"
1212
"github.com/customrealms/cli/internal/server"
13+
"golang.org/x/sync/errgroup"
1314
)
1415

1516
type ServeAction struct {
@@ -61,7 +62,7 @@ func copyFile(from, to string) error {
6162
return nil
6263
}
6364

64-
func (a *ServeAction) Run(ctx context.Context) error {
65+
func (a *ServeAction) Run(ctx context.Context, chanPluginUpdated <-chan struct{}) error {
6566

6667
// Check if Java is installed on the machine
6768
if _, err := exec.LookPath("java"); err != nil {
@@ -126,12 +127,39 @@ func (a *ServeAction) Run(ctx context.Context) error {
126127
fmt.Println("============================================================")
127128
fmt.Println()
128129

129-
// Run the server
130-
cmd := exec.CommandContext(ctx, "java", "-jar", jarBase, "-nogui")
131-
cmd.Dir = dir
132-
cmd.Stdin = os.Stdin
133-
cmd.Stdout = os.Stdout
134-
cmd.Stderr = os.Stderr
135-
return cmd.Run()
136-
130+
eg, ctx := errgroup.WithContext(ctx)
131+
132+
chanServerStopped := make(chan struct{})
133+
eg.Go(func() error {
134+
for {
135+
select {
136+
case <-ctx.Done():
137+
return ctx.Err()
138+
case <-chanServerStopped:
139+
return nil
140+
case _, ok := <-chanPluginUpdated:
141+
if !ok {
142+
return nil
143+
}
144+
}
145+
146+
// Copy the plugin file to the server
147+
if err := copyFile(a.PluginJarPath, filepath.Join(pluginsDir, filepath.Base(a.PluginJarPath))); err != nil {
148+
return err
149+
}
150+
fmt.Println("Plugin JAR updated. Run `/reload confirm` to reload the plugin.")
151+
}
152+
})
153+
eg.Go(func() error {
154+
defer close(chanServerStopped)
155+
156+
// Run the server
157+
cmd := exec.CommandContext(ctx, "java", "-jar", jarBase, "-nogui")
158+
cmd.Dir = dir
159+
cmd.Stdin = os.Stdin
160+
cmd.Stdout = os.Stdout
161+
cmd.Stderr = os.Stderr
162+
return cmd.Run()
163+
})
164+
return eg.Wait()
137165
}

0 commit comments

Comments
 (0)