Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 25 additions & 20 deletions cmd/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,30 @@ var envTemplateFileNames = []string{".env.example", ".env.template"}
var border = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).Padding(1).BorderForeground(lipgloss.AdaptiveColor{Light: "#999999", Dark: "#999999"})
var redDiff = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#990000", Dark: "#EE0000"})

func createProjectIgnoreRules(dir string, theproject *project.Project) *ignore.Rules {
// load up any gitignore files
gitignore := filepath.Join(dir, ignore.Ignore)
rules := ignore.Empty()
if util.Exists(gitignore) {
r, err := ignore.ParseFile(gitignore)
if err != nil {
errsystem.New(errsystem.ErrInvalidConfiguration, err,
errsystem.WithContextMessage("Error parsing .gitignore file")).ShowErrorAndExit()
}
rules = r
}
rules.AddDefaults()

// add any provider specific ignore rules
for _, rule := range theproject.Bundler.Ignore {
if err := rules.Add(rule); err != nil {
errsystem.New(errsystem.ErrInvalidConfiguration, err,
errsystem.WithContextMessage(fmt.Sprintf("Error adding project ignore rule: %s. %s", rule, err))).ShowErrorAndExit()
}
}
return rules
}

var cloudDeployCmd = &cobra.Command{
Use: "deploy",
Short: "Deploy project to the cloud",
Expand Down Expand Up @@ -444,26 +468,7 @@ Examples:
logger.Debug("saved project with updated Agents")
}

// load up any gitignore files
gitignore := filepath.Join(dir, ignore.Ignore)
rules := ignore.Empty()
if util.Exists(gitignore) {
r, err := ignore.ParseFile(gitignore)
if err != nil {
errsystem.New(errsystem.ErrInvalidConfiguration, err,
errsystem.WithContextMessage("Error parsing .gitignore file")).ShowErrorAndExit()
}
rules = r
}
rules.AddDefaults()

// add any provider specific ignore rules
for _, rule := range theproject.Bundler.Ignore {
if err := rules.Add(rule); err != nil {
errsystem.New(errsystem.ErrInvalidConfiguration, err,
errsystem.WithContextMessage(fmt.Sprintf("Error adding project ignore rule: %s. %s", rule, err))).ShowErrorAndExit()
}
}
rules := createProjectIgnoreRules(dir, theproject)

// create a temp file we're going to use for zip and upload
tmpfile, err := os.CreateTemp("", "agentuity-deploy-*.zip")
Expand Down
102 changes: 51 additions & 51 deletions cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"os"
"os/signal"
"path/filepath"
"sync"
"sync/atomic"
"syscall"
"time"

Expand All @@ -19,7 +21,6 @@ import (
"github.com/agentuity/cli/internal/util"
"github.com/agentuity/go-common/env"
"github.com/agentuity/go-common/tui"
"github.com/bep/debounce"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -51,7 +52,6 @@ Examples:
apiKey, userId := util.EnsureLoggedIn(ctx, log, cmd)
theproject := project.EnsureProject(ctx, cmd)
dir := theproject.Dir
isDeliberateRestart := false

checkForUpgrade(ctx, log, false)

Expand Down Expand Up @@ -117,7 +117,7 @@ Examples:
defer server.Close()

processCtx := context.Background()
var pid int
var pid int32

waitForConnection := func() {
if err := server.Connect(); err != nil {
Expand Down Expand Up @@ -167,26 +167,52 @@ Examples:
return ok
}

runServer := func() {
projectServerCmd, err = dev.CreateRunProjectCmd(processCtx, log, theproject, server, dir, orgId, port, os.Stdout, os.Stderr)
if err != nil {
errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithContextMessage("Failed to run project")).ShowErrorAndExit()
}
if err := projectServerCmd.Start(); err != nil {
errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithContextMessage(fmt.Sprintf("Failed to start project: %s", err))).ShowErrorAndExit()
}
atomic.StoreInt32(&pid, int32(projectServerCmd.Process.Pid))
// running = true
log.Trace("restarted project server (pid: %d)", projectServerCmd.Process.Pid)
log.Trace("waiting for project server to exit (pid: %d)", projectServerCmd.Process.Pid)
if err := projectServerCmd.Wait(); err != nil {
log.Error("project server (pid: %d) exited with error: %s", projectServerCmd.Process.Pid, err)
}
if projectServerCmd.ProcessState != nil {
log.Debug("project server (pid: %d) exited with code %d", projectServerCmd.Process.Pid, projectServerCmd.ProcessState.ExitCode())
} else {
log.Debug("project server (pid: %d) exited", projectServerCmd.Process.Pid)
}
}

// Initial build must exit if it fails
if !build(true) {
return
}

var restartingLock sync.Mutex

restart := func() {
isDeliberateRestart = true
build(false)
log.Debug("killing project server")
dev.KillProjectServer(log, projectServerCmd, pid)
log.Debug("killing project server done")
// prevent multiple restarts from happening at once
restartingLock.Lock()
defer restartingLock.Unlock()
dev.KillProjectServer(log, projectServerCmd, int(atomic.LoadInt32(&pid)))
if build(false) {
log.Trace("build ready")
go runServer()
}
}

// debounce a lot of changes at once to avoid multiple restarts in succession
debounced := debounce.New(250 * time.Millisecond)
rules := createProjectIgnoreRules(dir, theproject.Project)

// Watch for changes
watcher, err := dev.NewWatcher(log, dir, theproject.Project.Development.Watch.Files, func(path string) {
watcher, err := dev.NewWatcher(log, dir, rules, func(path string) {
log.Trace("%s has changed", path)
debounced(restart)
restart()
})
if err != nil {
errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithContextMessage(fmt.Sprintf("Failed to start watcher: %s", err))).ShowErrorAndExit()
Expand All @@ -199,12 +225,12 @@ Examples:
errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithContextMessage(fmt.Sprintf("Failed to start project: %s", err))).ShowErrorAndExit()
}

pid = projectServerCmd.Process.Pid
log.Trace("started project server with pid: %d", pid)
atomic.StoreInt32(&pid, int32(projectServerCmd.Process.Pid))
log.Trace("started project server with pid: %d", projectServerCmd.Process.Pid)

if err := server.HealthCheck(devModeUrl); err != nil {
log.Error("failed to health check connection: %s", err)
dev.KillProjectServer(log, projectServerCmd, pid)
dev.KillProjectServer(log, projectServerCmd, projectServerCmd.Process.Pid)
return
}
}
Expand All @@ -213,50 +239,24 @@ Examples:

log.Info("🚀 DevMode ready")

go func() {
for {
log.Trace("waiting for project server to exit (pid: %d)", pid)
if err := projectServerCmd.Wait(); err != nil {
if !isDeliberateRestart {
log.Error("project server (pid: %d) exited with error: %s", pid, err)
}
}
if projectServerCmd.ProcessState != nil {
log.Debug("project server (pid: %d) exited with code %d", pid, projectServerCmd.ProcessState.ExitCode())
} else {
log.Debug("project server (pid: %d) exited", pid)
}
log.Debug("isDeliberateRestart: %t, pid: %d", isDeliberateRestart, pid)
if !isDeliberateRestart {
return
}

// If it was a deliberate restart, start the new process here
if isDeliberateRestart {
isDeliberateRestart = false
log.Trace("restarting project server")
projectServerCmd, err = dev.CreateRunProjectCmd(processCtx, log, theproject, server, dir, orgId, port, os.Stdout, os.Stderr)
if err != nil {
errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithContextMessage("Failed to run project")).ShowErrorAndExit()
}
if err := projectServerCmd.Start(); err != nil {
errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithContextMessage(fmt.Sprintf("Failed to start project: %s", err))).ShowErrorAndExit()
}
pid = projectServerCmd.Process.Pid
log.Trace("restarted project server (pid: %d)", pid)
}
}
}()

teardown := func() {
restartingLock.Lock()
defer restartingLock.Unlock()
watcher.Close(log)
server.Close()
dev.KillProjectServer(log, projectServerCmd, pid)
if projectServerCmd != nil {
dev.KillProjectServer(log, projectServerCmd, int(atomic.LoadInt32(&pid)))
projectServerCmd.Wait()
}
}

<-ctx.Done()

fmt.Printf("\b\b\033[K") // remove the ^C

teardown()

log.Info("👋 See you next time!")
},
}

Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ require (
github.com/Masterminds/semver v1.5.0
github.com/agentuity/go-common v1.0.64
github.com/agentuity/mcp-golang/v2 v2.0.2
github.com/bep/debounce v1.2.1
github.com/bmatcuk/doublestar/v4 v4.8.1
github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v1.3.4
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
Expand Down
25 changes: 25 additions & 0 deletions internal/bundler/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,27 @@ func installSourceMapSupportIfNeeded(ctx BundleContext, dir string) error {
return nil
}

func runTypecheck(ctx BundleContext, dir string) error {
tsc := filepath.Join(dir, "node_modules", ".bin", "tsc")
if !util.Exists(tsc) {
ctx.Logger.Warn("no tsc found at %s, skipping typecheck", tsc)
return nil
}
cmd := exec.CommandContext(ctx.Context, tsc, "--noEmit")
cmd.Dir = dir
cmd.Stdout = ctx.Writer
cmd.Stderr = ctx.Writer
if err := cmd.Run(); err != nil {
if ctx.DevMode {
ctx.Logger.Error("🚫 TypeScript check failed")
return ErrBuildFailed // output goes to the console so we don't need to show it
}
os.Exit(2)
}
ctx.Logger.Debug("✅ TypeScript passed")
return nil
}

func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject *project.Project) error {

if ctx.Install || !util.Exists(filepath.Join(dir, "node_modules")) {
Expand Down Expand Up @@ -167,6 +188,10 @@ func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject *
return err
}

if err := runTypecheck(ctx, dir); err != nil {
return err
}

var entryPoints []string
entryPoints = append(entryPoints, filepath.Join(dir, "index.js"))
files, err := util.ListDir(filepath.Join(dir, theproject.Bundler.AgentConfig.Dir))
Expand Down
Loading
Loading