Skip to content

Commit

Permalink
feat: rebuild plugin on file changes while running dev server
Browse files Browse the repository at this point in the history
  • Loading branch information
connerdouglass committed Jun 25, 2024
1 parent 654bec7 commit 4e05fb0
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 25 deletions.
87 changes: 79 additions & 8 deletions cmd/cmd_run.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package main

import (
"errors"
"log"
"os"
"path/filepath"

"github.com/customrealms/cli/internal/build"
"github.com/customrealms/cli/internal/project"
"github.com/customrealms/cli/internal/serve"
"github.com/customrealms/cli/internal/server"
"github.com/fsnotify/fsnotify"
"golang.org/x/sync/errgroup"
)

type RunCmd struct {
ProjectDir string `name:"project" short:"p" usage:"plugin project directory" optional:""`
McVersion string `name:"mc" short:"mc" usage:"Minecraft version number target" optional:""`
McVersion string `name:"mc" usage:"Minecraft version number target" optional:""`
TemplateJarFile string `name:"jar" short:"t" usage:"template JAR file" optional:""`
}

Expand Down Expand Up @@ -70,11 +75,77 @@ func (c *RunCmd) Run() error {
return err
}

// Create the serve runner
serveAction := serve.ServeAction{
MinecraftVersion: minecraftVersion,
PluginJarPath: outputFile,
ServerJarFetcher: serverJarFetcher,
}
return serveAction.Run(ctx)
eg, ctx := errgroup.WithContext(ctx)

chanPluginUpdated := make(chan struct{})
chanServerStopped := make(chan struct{})
eg.Go(func() error {
// Create new watcher.
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
defer watcher.Close()

// Start listening for events.
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Has(fsnotify.Write) {
// Rebuild the plugin JAR file
if err := buildAction.Run(ctx); err != nil {
log.Println("Error: ", err)
} else {
select {
case chanPluginUpdated <- struct{}{}:
case <-ctx.Done():
return
}
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
case <-ctx.Done():
return
case <-chanServerStopped:
return
}
}
}()

// Add the project directory and src directory to the watcher
if err := errors.Join(
watcher.Add(c.ProjectDir),
watcher.Add(filepath.Join(c.ProjectDir, "src")),
); err != nil {
return err
}

// Block until the server is stopped
select {
case <-ctx.Done():
return ctx.Err()
case <-chanServerStopped:
return nil
}
})
eg.Go(func() error {
defer close(chanServerStopped)

// Create the serve runner
serveAction := serve.ServeAction{
MinecraftVersion: minecraftVersion,
PluginJarPath: outputFile,
ServerJarFetcher: serverJarFetcher,
}
return serveAction.Run(ctx, chanPluginUpdated)
})
return eg.Wait()
}
2 changes: 1 addition & 1 deletion cmd/cmd_yml.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

type YmlCmd struct {
ProjectDir string `name:"project" short:"p" usage:"plugin project directory" optional:""`
McVersion string `name:"mc" short:"mc" usage:"Minecraft version number target" optional:""`
McVersion string `name:"mc" usage:"Minecraft version number target" optional:""`
}

func (c *YmlCmd) Run() error {
Expand Down
10 changes: 5 additions & 5 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import (
)

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

func rootContext() (context.Context, context.CancelFunc) {
Expand Down
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@ module github.com/customrealms/cli

go 1.22

require github.com/alecthomas/kong v0.9.0
require (
github.com/alecthomas/kong v0.9.0
github.com/fsnotify/fsnotify v1.7.0
golang.org/x/sync v0.7.0
)

require golang.org/x/sys v0.4.0 // indirect
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@ github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA
github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
3 changes: 2 additions & 1 deletion internal/minecraft/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type paperMcBuild struct {

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

func downloadJSON[T any](ctx context.Context, url string) (*T, error) {
fmt.Println(url)
// Create the HTTP request
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
Expand Down
46 changes: 37 additions & 9 deletions internal/serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/customrealms/cli/internal/minecraft"
"github.com/customrealms/cli/internal/server"
"golang.org/x/sync/errgroup"
)

type ServeAction struct {
Expand Down Expand Up @@ -61,7 +62,7 @@ func copyFile(from, to string) error {
return nil
}

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

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

// Run the server
cmd := exec.CommandContext(ctx, "java", "-jar", jarBase, "-nogui")
cmd.Dir = dir
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()

eg, ctx := errgroup.WithContext(ctx)

chanServerStopped := make(chan struct{})
eg.Go(func() error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-chanServerStopped:
return nil
case _, ok := <-chanPluginUpdated:
if !ok {
return nil
}
}

// Copy the plugin file to the server
if err := copyFile(a.PluginJarPath, filepath.Join(pluginsDir, filepath.Base(a.PluginJarPath))); err != nil {
return err
}
fmt.Println("Plugin JAR updated. Run `/reload confirm` to reload the plugin.")
}
})
eg.Go(func() error {
defer close(chanServerStopped)

// Run the server
cmd := exec.CommandContext(ctx, "java", "-jar", jarBase, "-nogui")
cmd.Dir = dir
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
})
return eg.Wait()
}

0 comments on commit 4e05fb0

Please sign in to comment.