From 91155b42a32a5eb4f0330974a7fd72136d8c344a Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Tue, 20 May 2025 15:21:01 -0500 Subject: [PATCH 1/6] Devmode: Improved logging output --- .github/workflows/release.yml | 3 +- cmd/dev.go | 7 +-- internal/dev/dev.go | 6 +-- internal/dev/pending_logger.go | 36 ++++++++++--- internal/dev/tui.go | 39 +++++++++++++- internal/dev/tui_logger.go | 95 ++++++++++++++++++++++------------ 6 files changed, 137 insertions(+), 49 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 35c846c2..6479acef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ permissions: jobs: release: - runs-on: windows-latest # required to use wix + runs-on: blacksmith-4vcpu-ubuntu-2204 steps: - name: Checkout uses: actions/checkout@v4 @@ -38,3 +38,4 @@ jobs: - uses: actions/attest-build-provenance@v2 with: subject-checksums: ./dist/checksums.txt + subject-checksums-type: sha256 diff --git a/cmd/dev.go b/cmd/dev.go index 430c74fb..7bf77b58 100644 --- a/cmd/dev.go +++ b/cmd/dev.go @@ -124,7 +124,8 @@ Examples: defer ui.Close(false) - tuiLogger := dev.NewTUILogger(logLevel, ui) + tuiLogger := dev.NewTUILogger(logLevel, ui, dev.Stdout) + tuiLoggerErr := dev.NewTUILogger(logLevel, ui, dev.StdErr) if err := server.Connect(ui, tuiLogger); err != nil { log.Error("failed to start live dev connection: %s", err) @@ -139,7 +140,7 @@ Examples: agent.PublicURL = fmt.Sprintf("%s/%s", publicUrl, agent.ID) } - projectServerCmd, err := dev.CreateRunProjectCmd(processCtx, tuiLogger, theproject, server, dir, orgId, port, tuiLogger) + projectServerCmd, err := dev.CreateRunProjectCmd(processCtx, tuiLogger, theproject, server, dir, orgId, port, tuiLogger, tuiLoggerErr) if err != nil { errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithContextMessage("Failed to run project")).ShowErrorAndExit() } @@ -241,7 +242,7 @@ Examples: if isDeliberateRestart { isDeliberateRestart = false tuiLogger.Trace("restarting project server") - projectServerCmd, err = dev.CreateRunProjectCmd(processCtx, tuiLogger, theproject, server, dir, orgId, port, tuiLogger) + projectServerCmd, err = dev.CreateRunProjectCmd(processCtx, tuiLogger, theproject, server, dir, orgId, port, tuiLogger, tuiLoggerErr) if err != nil { errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithContextMessage("Failed to run project")).ShowErrorAndExit() } diff --git a/internal/dev/dev.go b/internal/dev/dev.go index c3a91f9a..dc1cc761 100644 --- a/internal/dev/dev.go +++ b/internal/dev/dev.go @@ -104,7 +104,7 @@ func FindAvailablePort(p project.ProjectContext, tryPort int) (int, error) { return findAvailablePort() } -func CreateRunProjectCmd(ctx context.Context, log logger.Logger, theproject project.ProjectContext, server *Server, dir string, orgId string, port int, writer io.Writer) (*exec.Cmd, error) { +func CreateRunProjectCmd(ctx context.Context, log logger.Logger, theproject project.ProjectContext, server *Server, dir string, orgId string, port int, stdout io.Writer, stderr io.Writer) (*exec.Cmd, error) { // set the vars projectServerCmd := exec.CommandContext(ctx, theproject.Project.Development.Command, theproject.Project.Development.Args...) projectServerCmd.Env = os.Environ()[:] @@ -128,8 +128,8 @@ func CreateRunProjectCmd(ctx context.Context, log logger.Logger, theproject proj projectServerCmd.Env = append(projectServerCmd.Env, fmt.Sprintf("AGENTUITY_CLOUD_PORT=%d", port)) projectServerCmd.Env = append(projectServerCmd.Env, fmt.Sprintf("PORT=%d", port)) - projectServerCmd.Stdout = writer - projectServerCmd.Stderr = writer + projectServerCmd.Stdout = stdout + projectServerCmd.Stderr = stderr projectServerCmd.Dir = dir util.ProcessSetup(projectServerCmd) diff --git a/internal/dev/pending_logger.go b/internal/dev/pending_logger.go index 2453eacc..239e5dc2 100644 --- a/internal/dev/pending_logger.go +++ b/internal/dev/pending_logger.go @@ -10,8 +10,13 @@ import ( "github.com/agentuity/go-common/tui" ) +type pendingLog struct { + level logger.LogLevel + msg string +} + type PendingLogger struct { - pending []string + pending []pendingLog logLevel logger.LogLevel logger logger.Logger mutex sync.RWMutex @@ -21,7 +26,7 @@ var _ logger.Logger = (*PendingLogger)(nil) func NewPendingLogger(logLevel logger.LogLevel) *PendingLogger { return &PendingLogger{ - pending: make([]string, 0), + pending: make([]pendingLog, 0), logLevel: logLevel, } } @@ -30,7 +35,7 @@ func (l *PendingLogger) drain(ui *DevModeUI, logger logger.Logger) { l.mutex.Lock() defer l.mutex.Unlock() for _, val := range l.pending { - ui.AddLog("%s", val) + ui.AddLog(val.level, "%s", val.msg) } l.logger = logger l.pending = nil @@ -62,7 +67,10 @@ func (l *PendingLogger) Trace(msg string, args ...interface{}) { l.logger.Trace(msg, args...) return } - val := tui.Muted("[TRACE] " + fmt.Sprintf(msg, args...)) + val := pendingLog{ + level: logger.LevelTrace, + msg: fmt.Sprintf(msg, args...), + } l.pending = append(l.pending, val) } @@ -77,7 +85,10 @@ func (l *PendingLogger) Debug(msg string, args ...interface{}) { l.logger.Debug(msg, args...) return } - val := tui.Muted("[TRACE] " + fmt.Sprintf(msg, args...)) + val := pendingLog{ + level: logger.LevelDebug, + msg: fmt.Sprintf(msg, args...), + } l.pending = append(l.pending, val) } @@ -92,7 +103,10 @@ func (l *PendingLogger) Info(msg string, args ...interface{}) { l.logger.Info(msg, args...) return } - val := tui.Text("[INFO] " + fmt.Sprintf(msg, args...)) + val := pendingLog{ + level: logger.LevelInfo, + msg: fmt.Sprintf(msg, args...), + } l.pending = append(l.pending, val) } @@ -107,7 +121,10 @@ func (l *PendingLogger) Warn(msg string, args ...interface{}) { l.logger.Warn(msg, args...) return } - val := tui.Title("[WARN] " + fmt.Sprintf(msg, args...)) + val := pendingLog{ + level: logger.LevelWarn, + msg: fmt.Sprintf(msg, args...), + } l.pending = append(l.pending, val) } @@ -122,7 +139,10 @@ func (l *PendingLogger) Error(msg string, args ...interface{}) { l.logger.Error(msg, args...) return } - val := tui.Bold("[ERROR] " + fmt.Sprintf(msg, args...)) + val := pendingLog{ + level: logger.LevelError, + msg: fmt.Sprintf(msg, args...), + } l.pending = append(l.pending, val) } diff --git a/internal/dev/tui.go b/internal/dev/tui.go index 36d8a79e..8e59272b 100644 --- a/internal/dev/tui.go +++ b/internal/dev/tui.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/agentuity/go-common/logger" "github.com/agentuity/go-common/tui" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" @@ -29,6 +30,10 @@ var ( runningColor = lipgloss.AdaptiveColor{Light: "#00FF00", Dark: "#009900"} pausedColor = lipgloss.AdaptiveColor{Light: "#FFA500", Dark: "#FFA500"} statusColor = lipgloss.AdaptiveColor{Light: "#750075", Dark: "#FF5CFF"} + errorColor = lipgloss.AdaptiveColor{Light: "#FF0000", Dark: "#EE0000"} + warningColor = lipgloss.AdaptiveColor{Light: "#FFFF00", Dark: "#FFFF00"} + errorStyle = lipgloss.NewStyle().Foreground(errorColor) + warningStyle = lipgloss.NewStyle().Foreground(warningColor) runningStyle = lipgloss.NewStyle().Foreground(runningColor) pausedStyle = lipgloss.NewStyle().Foreground(pausedColor).AlignHorizontal(lipgloss.Center) labelStyle = lipgloss.NewStyle().Foreground(labelColor).Bold(true) @@ -61,11 +66,25 @@ type spinnerStopMsg struct{} type logItem struct { timestamp time.Time + severity logger.LogLevel message string raw string } -func (i logItem) Title() string { return i.message } +func (i logItem) Title() string { + switch i.severity { + case logger.LevelError: + return errorStyle.Render(i.message) + case logger.LevelDebug, logger.LevelTrace: + return tui.Muted(i.message) + case logger.LevelInfo: + return i.message + case logger.LevelWarn: + return warningStyle.Render(i.message) + default: + return i.message + } +} func (i logItem) Description() string { return "" } func (i logItem) FilterValue() string { return i.message } @@ -502,7 +521,7 @@ func (d *DevModeUI) Start() { } // Add a log message to the log list -func (d *DevModeUI) AddLog(log string, args ...any) { +func (d *DevModeUI) AddLog(severity logger.LogLevel, log string, args ...any) { if !d.enabled { fmt.Println(fmt.Sprintf(log, args...)) return @@ -510,11 +529,27 @@ func (d *DevModeUI) AddLog(log string, args ...any) { raw := fmt.Sprintf(log, args...) d.program.Send(addLogMsg{ timestamp: time.Now(), + severity: severity, raw: raw, message: strings.ReplaceAll(ansiColorStripper.ReplaceAllString(raw, ""), "\n", " "), }) } +// Add an error log message to the log list +func (d *DevModeUI) AddErrorLog(log string, args ...any) { + if !d.enabled { + fmt.Println(errorStyle.Render(fmt.Sprintf(log, args...))) + return + } + raw := fmt.Sprintf(log, args...) + d.program.Send(addLogMsg{ + timestamp: time.Now(), + severity: logger.LevelError, + raw: raw, + message: errorStyle.Render(strings.ReplaceAll(ansiColorStripper.ReplaceAllString(raw, ""), "\n", " ")), + }) +} + func (d *DevModeUI) SetStatusMessage(msg string, args ...any) { val := fmt.Sprintf(msg, args...) if !d.enabled { diff --git a/internal/dev/tui_logger.go b/internal/dev/tui_logger.go index f0f52eeb..55f02a97 100644 --- a/internal/dev/tui_logger.go +++ b/internal/dev/tui_logger.go @@ -13,13 +13,22 @@ import ( "github.com/agentuity/go-common/tui" ) +type ioType int + +const ( + StdErr ioType = iota + Stdout +) + type TuiLogger struct { logLevel logger.LogLevel ui *DevModeUI + ioType ioType + pending bytes.Buffer } -func NewTUILogger(logLevel logger.LogLevel, ui *DevModeUI) *TuiLogger { - return &TuiLogger{logLevel: logLevel, ui: ui} +func NewTUILogger(logLevel logger.LogLevel, ui *DevModeUI, ioType ioType) *TuiLogger { + return &TuiLogger{logLevel: logLevel, ui: ui, ioType: ioType} } var _ logger.Logger = (*TuiLogger)(nil) @@ -45,8 +54,7 @@ func (l *TuiLogger) Trace(msg string, args ...interface{}) { if logger.LevelTrace < l.logLevel { return } - val := tui.Muted("[TRACE] " + fmt.Sprintf(msg, args...)) - l.ui.AddLog("%s", val) + l.ui.AddLog(logger.LevelTrace, "[TRACE] %s", fmt.Sprintf(msg, args...)) } // Debug level logging @@ -54,8 +62,7 @@ func (l *TuiLogger) Debug(msg string, args ...interface{}) { if logger.LevelDebug < l.logLevel { return } - val := tui.Muted("[TRACE] " + fmt.Sprintf(msg, args...)) - l.ui.AddLog("%s", val) + l.ui.AddLog(logger.LevelDebug, "[DEBUG] %s", fmt.Sprintf(msg, args...)) } // Info level loggi ng @@ -63,8 +70,7 @@ func (l *TuiLogger) Info(msg string, args ...interface{}) { if logger.LevelInfo < l.logLevel { return } - val := tui.Text("[INFO] " + fmt.Sprintf(msg, args...)) - l.ui.AddLog("%s", val) + l.ui.AddLog(logger.LevelInfo, "[INFO] %s", fmt.Sprintf(msg, args...)) } // Warning level logging @@ -72,8 +78,7 @@ func (l *TuiLogger) Warn(msg string, args ...interface{}) { if logger.LevelWarn < l.logLevel { return } - val := tui.Title("[WARN] " + fmt.Sprintf(msg, args...)) - l.ui.AddLog("%s", val) + l.ui.AddLog(logger.LevelWarn, "[WARN] %s", fmt.Sprintf(msg, args...)) } // Error level logging @@ -81,14 +86,13 @@ func (l *TuiLogger) Error(msg string, args ...interface{}) { if logger.LevelError < l.logLevel { return } - val := tui.Bold("[ERROR] " + fmt.Sprintf(msg, args...)) - l.ui.AddLog("%s", val) + l.ui.AddLog(logger.LevelError, "[ERROR] %s", fmt.Sprintf(msg, args...)) } // Fatal level logging and exit with code 1 func (l *TuiLogger) Fatal(msg string, args ...interface{}) { val := tui.Bold("[FATAL] " + fmt.Sprintf(msg, args...)) - l.ui.AddLog("%s", val) + l.ui.AddLog(logger.LevelError, "%s", val) os.Exit(1) } @@ -101,31 +105,58 @@ var eol = []byte("\n") var ansiColorStripper = regexp.MustCompile("\x1b\\[[0-9;]*[mK]") func (l *TuiLogger) Write(p []byte) (n int, err error) { - trimmed := bytes.Split(p, eol) + l.pending.Write(p) + + if !bytes.HasSuffix(l.pending.Bytes(), eol) { + return len(p), nil + } + + trimmed := bytes.Split(l.pending.Bytes(), eol) + l.pending.Reset() + for _, line := range trimmed { if len(line) == 0 { continue } - log := string(line) - if len(log) > 20 { - prefix := ansiColorStripper.ReplaceAllString(log[:20], "") - if logger.LevelTrace < l.logLevel && strings.HasPrefix(prefix, "[TRACE]") { - continue - } - if logger.LevelDebug < l.logLevel && strings.HasPrefix(prefix, "[DEBUG]") { - continue - } - if logger.LevelInfo < l.logLevel && strings.HasPrefix(prefix, "[INFO]") { - continue - } - if logger.LevelWarn < l.logLevel && strings.HasPrefix(prefix, "[WARN]") { - continue - } - if logger.LevelError < l.logLevel && strings.HasPrefix(prefix, "[ERROR]") { - continue + log := ansiColorStripper.ReplaceAllString(string(line), "") + severity := logger.LevelTrace + var prefix string + if len(log) > 9 { + bracket := strings.Index(log, "] ") + prefix = strings.TrimSpace(log[:bracket+2]) + if strings.HasPrefix(prefix, "[TRACE]") { + severity = logger.LevelTrace + if logger.LevelTrace < l.logLevel { + continue + } + } else if strings.HasPrefix(prefix, "[DEBUG]") { + severity = logger.LevelDebug + if logger.LevelDebug < l.logLevel { + continue + } + } else if strings.HasPrefix(prefix, "[INFO]") { + severity = logger.LevelInfo + if logger.LevelInfo < l.logLevel { + continue + } + } else if strings.HasPrefix(prefix, "[WARN]") { + severity = logger.LevelWarn + if logger.LevelWarn < l.logLevel { + continue + } + } else if strings.HasPrefix(prefix, "[ERROR]") { + severity = logger.LevelError + if logger.LevelError < l.logLevel { + continue + } } + log = strings.TrimPrefix(log[bracket+2:], " ") + } + if l.ioType == Stdout { + l.ui.AddLog(severity, "%s %s", prefix, log) + } else { + l.ui.AddErrorLog("%s", log) } - l.ui.AddLog("%s", log) } return len(p), nil } From b96dcd4a6e9d1ba5d7d9d053806d4bcb9fd311c7 Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Tue, 20 May 2025 15:24:09 -0500 Subject: [PATCH 2/6] make sure we drain stderr first --- cmd/dev.go | 2 +- internal/dev/server.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/dev.go b/cmd/dev.go index 7bf77b58..b1c9ce64 100644 --- a/cmd/dev.go +++ b/cmd/dev.go @@ -127,7 +127,7 @@ Examples: tuiLogger := dev.NewTUILogger(logLevel, ui, dev.Stdout) tuiLoggerErr := dev.NewTUILogger(logLevel, ui, dev.StdErr) - if err := server.Connect(ui, tuiLogger); err != nil { + if err := server.Connect(ui, tuiLogger, tuiLoggerErr); err != nil { log.Error("failed to start live dev connection: %s", err) ui.Close(true) return diff --git a/internal/dev/server.go b/internal/dev/server.go index e4318944..b18ff39e 100644 --- a/internal/dev/server.go +++ b/internal/dev/server.go @@ -547,8 +547,11 @@ func (s *Server) HealthCheck(devModeUrl string) error { return fmt.Errorf("health check failed after %s", time.Since(started)) } -func (s *Server) Connect(ui *DevModeUI, tuiLogger logger.Logger) error { +func (s *Server) Connect(ui *DevModeUI, tuiLogger logger.Logger, tuiLoggerErr logger.Logger) error { s.logger = tuiLogger + if pl, ok := tuiLoggerErr.(*PendingLogger); ok { + pl.drain(ui, tuiLoggerErr) + } if pl, ok := s.logger.(*PendingLogger); ok { pl.drain(ui, s.logger) } From 08ab27284ec76480c88ca4bc2543c0969c8652f1 Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Tue, 20 May 2025 22:20:53 -0500 Subject: [PATCH 3/6] add sourcemap support --- internal/bundler/bundler.go | 29 +++++++++++++++++- internal/bundler/shim.go | 61 ++++++++++++++++++++++++++++++++++++- internal/dev/dev.go | 12 ++++++++ internal/dev/tui_logger.go | 2 ++ 4 files changed, 102 insertions(+), 2 deletions(-) diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index 5a0e810b..63d5e566 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -42,6 +42,21 @@ type BundleContext struct { Writer io.Writer } +func installSourceMapSupportIfNeeded(ctx BundleContext, dir string) error { + // only bun needs to install this library to aide in parsing the source maps + path := filepath.Join(dir, "node_modules", "source-map-js", "package.json") + if !util.Exists(path) { + cmd := exec.CommandContext(ctx.Context, "bun", "install", "source-map-js", "--no-save", "--silent", "--no-progress", "--no-summary", "--ignore-scripts") + cmd.Dir = dir + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to install source-map-js: %w. %s", err, string(out)) + } + return nil + } + return nil +} + func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject *project.Project) error { if ctx.Install || !util.Exists(filepath.Join(dir, "node_modules")) { @@ -79,6 +94,12 @@ func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject * ctx.Logger.Debug("installed dependencies: %s", strings.TrimSpace(string(out))) } + if theproject.Bundler.Runtime == "bunjs" { + if err := installSourceMapSupportIfNeeded(ctx, dir); err != nil { + return fmt.Errorf("failed to install bun source-map-support: %w", err) + } + } + if err := checkForBreakingChanges(ctx, "javascript", theproject.Bundler.Runtime); err != nil { return err } @@ -130,6 +151,12 @@ func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject * } defines["process.env.AGENTUITY_CLOUD_AGENTS_JSON"] = cstr.JSONStringify(cstr.JSONStringify(agents)) + var postShim string + // only bun needs this shim + if theproject.Bundler.Runtime == "bunjs" { + postShim = sourceMapShim + } + ctx.Logger.Debug("starting build") started := time.Now() result := api.Build(api.BuildOptions{ @@ -153,7 +180,7 @@ func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject * Define: defines, LegalComments: api.LegalCommentsNone, Banner: map[string]string{ - "js": jsheader + jsshim, + "js": jsheader + jsshim + postShim, }, }) ctx.Logger.Debug("finished build in %v", time.Since(started)) diff --git a/internal/bundler/shim.go b/internal/bundler/shim.go index e34ac1b4..6eb4c0a0 100644 --- a/internal/bundler/shim.go +++ b/internal/bundler/shim.go @@ -7,6 +7,7 @@ import { createRequire as __agentuity_createRequire } from 'module'; const require = __agentuity_createRequire(import.meta.url); import { fileURLToPath as __agentuity_fileURLToPath } from 'url'; import { dirname as __agentuity_dirname } from 'path'; +import { readFileSync as __agentuity_readFileSync, existsSync as __agentuity_existsSync } from 'fs'; const __filename = __agentuity_fileURLToPath(import.meta.url); const __dirname = __agentuity_dirname(__filename); @@ -45,4 +46,62 @@ globalThis.__require = (id) => { } } throw new Error('Dynamic require of ' + id + ' is not supported'); -};` +}; +` + +// NOTE: this shim is only used in bun since node has built-in source map support +var sourceMapShim = ` +(function () { +const { SourceMapConsumer: __agentuity_SourceMapConsumer } = require('source-map-js'); +const { join: __agentuity_join } = require('path'); +const __prepareStackTrace = Error.prepareStackTrace; +const __cachedSourceMap = {}; +function getSourceMap(filename) { + if (filename in __cachedSourceMap) { + return __cachedSourceMap[filename]; + } + if (!__agentuity_existsSync(filename)) { + return null; + } + const sm = new __agentuity_SourceMapConsumer(__agentuity_readFileSync(filename).toString()); + __cachedSourceMap[filename] = sm; + return sm; +} +const frameRegex = /(.+)\((.+):(\d+):(\d+)\)$/; +Error.prepareStackTrace = function (err, stack) { + const _stack = __prepareStackTrace(err, stack); + const tok = _stack.split('\n'); + const lines = []; + for (const t of tok) { + if (t.includes('.agentuity/') && frameRegex.test(t)) { + const parts = frameRegex.exec(t); + if (parts.length === 5) { + const filename = parts[2]; + const sm = getSourceMap(filename+'.map'); + if (sm) { + const lineno = parts[3]; + const colno = parts[4]; + const pos = sm.originalPositionFor({ + line: +lineno, + column: +colno, + }) + if (pos && pos.source) { + const startIndex = filename.indexOf('.agentuity/'); + const offset = filename.includes('../node_modules/') ? 11 : 0; + const basedir = filename.substring(0, startIndex + offset); + const sourceOffset = pos.source.indexOf('src/'); + const source = pos.source.substring(sourceOffset); + const newfile = __agentuity_join(basedir, source); + const newline = parts[1] + '(' + newfile + ':' + pos.line + ':' + pos.column + ')'; + lines.push(newline); + continue; + } + } + } + } + lines.push(t); + } + return lines.join('\n'); +}; +})(); +` diff --git a/internal/dev/dev.go b/internal/dev/dev.go index dc1cc761..335c1f2e 100644 --- a/internal/dev/dev.go +++ b/internal/dev/dev.go @@ -125,6 +125,18 @@ func CreateRunProjectCmd(ctx context.Context, log logger.Logger, theproject proj projectServerCmd.Env = append(projectServerCmd.Env, "NODE_ENV=development") } + // for nodejs, we need to enable source maps directly in the environment. + // for bun, we need to inject a shim helper to parse the source maps + if theproject.Project.Bundler.Runtime == "nodejs" { + nodeOptions := os.Getenv("NODE_OPTIONS") + if nodeOptions == "" { + nodeOptions = "--enable-source-maps" + } else { + nodeOptions = fmt.Sprintf("%s --enable-source-maps", nodeOptions) + } + projectServerCmd.Env = append(projectServerCmd.Env, nodeOptions) + } + projectServerCmd.Env = append(projectServerCmd.Env, fmt.Sprintf("AGENTUITY_CLOUD_PORT=%d", port)) projectServerCmd.Env = append(projectServerCmd.Env, fmt.Sprintf("PORT=%d", port)) diff --git a/internal/dev/tui_logger.go b/internal/dev/tui_logger.go index 55f02a97..a45bd9b9 100644 --- a/internal/dev/tui_logger.go +++ b/internal/dev/tui_logger.go @@ -139,11 +139,13 @@ func (l *TuiLogger) Write(p []byte) (n int, err error) { if logger.LevelInfo < l.logLevel { continue } + prefix += " " } else if strings.HasPrefix(prefix, "[WARN]") { severity = logger.LevelWarn if logger.LevelWarn < l.logLevel { continue } + prefix += " " } else if strings.HasPrefix(prefix, "[ERROR]") { severity = logger.LevelError if logger.LevelError < l.logLevel { From cfc4895ef7429d4abc0b7e689872daaddc809c9c Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Tue, 20 May 2025 22:35:28 -0500 Subject: [PATCH 4/6] feedback --- .github/workflows/release.yml | 1 - internal/bundler/bundler.go | 4 ++-- internal/dev/tui.go | 2 +- internal/dev/tui_logger.go | 15 +++++++++++---- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6479acef..00aef72b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,4 +38,3 @@ jobs: - uses: actions/attest-build-provenance@v2 with: subject-checksums: ./dist/checksums.txt - subject-checksums-type: sha256 diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index 63d5e566..70892925 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -46,7 +46,7 @@ func installSourceMapSupportIfNeeded(ctx BundleContext, dir string) error { // only bun needs to install this library to aide in parsing the source maps path := filepath.Join(dir, "node_modules", "source-map-js", "package.json") if !util.Exists(path) { - cmd := exec.CommandContext(ctx.Context, "bun", "install", "source-map-js", "--no-save", "--silent", "--no-progress", "--no-summary", "--ignore-scripts") + cmd := exec.CommandContext(ctx.Context, "bun", "add", "source-map-js", "--no-save", "--silent", "--no-progress", "--no-summary", "--ignore-scripts") cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { @@ -180,7 +180,7 @@ func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject * Define: defines, LegalComments: api.LegalCommentsNone, Banner: map[string]string{ - "js": jsheader + jsshim + postShim, + "js": strings.Join([]string{jsheader, jsshim, postShim}, "\n"), }, }) ctx.Logger.Debug("finished build in %v", time.Since(started)) diff --git a/internal/dev/tui.go b/internal/dev/tui.go index 8e59272b..7bd18587 100644 --- a/internal/dev/tui.go +++ b/internal/dev/tui.go @@ -546,7 +546,7 @@ func (d *DevModeUI) AddErrorLog(log string, args ...any) { timestamp: time.Now(), severity: logger.LevelError, raw: raw, - message: errorStyle.Render(strings.ReplaceAll(ansiColorStripper.ReplaceAllString(raw, ""), "\n", " ")), + message: strings.ReplaceAll(ansiColorStripper.ReplaceAllString(raw, ""), "\n", " "), }) } diff --git a/internal/dev/tui_logger.go b/internal/dev/tui_logger.go index a45bd9b9..354bd94f 100644 --- a/internal/dev/tui_logger.go +++ b/internal/dev/tui_logger.go @@ -8,6 +8,7 @@ import ( "os" "regexp" "strings" + "sync" "github.com/agentuity/go-common/logger" "github.com/agentuity/go-common/tui" @@ -25,6 +26,7 @@ type TuiLogger struct { ui *DevModeUI ioType ioType pending bytes.Buffer + mu sync.Mutex } func NewTUILogger(logLevel logger.LogLevel, ui *DevModeUI, ioType ioType) *TuiLogger { @@ -105,6 +107,8 @@ var eol = []byte("\n") var ansiColorStripper = regexp.MustCompile("\x1b\\[[0-9;]*[mK]") func (l *TuiLogger) Write(p []byte) (n int, err error) { + l.mu.Lock() + defer l.mu.Unlock() l.pending.Write(p) if !bytes.HasSuffix(l.pending.Bytes(), eol) { @@ -123,7 +127,13 @@ func (l *TuiLogger) Write(p []byte) (n int, err error) { var prefix string if len(log) > 9 { bracket := strings.Index(log, "] ") - prefix = strings.TrimSpace(log[:bracket+2]) + if bracket == -1 { + // No prefix – treat the entire line as the message + prefix = "" + } else { + prefix = strings.TrimSpace(log[:bracket+2]) + log = strings.TrimPrefix(log[bracket+2:], " ") + } if strings.HasPrefix(prefix, "[TRACE]") { severity = logger.LevelTrace if logger.LevelTrace < l.logLevel { @@ -139,20 +149,17 @@ func (l *TuiLogger) Write(p []byte) (n int, err error) { if logger.LevelInfo < l.logLevel { continue } - prefix += " " } else if strings.HasPrefix(prefix, "[WARN]") { severity = logger.LevelWarn if logger.LevelWarn < l.logLevel { continue } - prefix += " " } else if strings.HasPrefix(prefix, "[ERROR]") { severity = logger.LevelError if logger.LevelError < l.logLevel { continue } } - log = strings.TrimPrefix(log[bracket+2:], " ") } if l.ioType == Stdout { l.ui.AddLog(severity, "%s %s", prefix, log) From 0ab622a5ac3920636a4016829ae64200671adf31 Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Wed, 21 May 2025 09:41:56 -0500 Subject: [PATCH 5/6] code rabbit suggestion --- internal/dev/tui_logger.go | 42 ++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/internal/dev/tui_logger.go b/internal/dev/tui_logger.go index 354bd94f..bb267897 100644 --- a/internal/dev/tui_logger.go +++ b/internal/dev/tui_logger.go @@ -106,6 +106,16 @@ func (l *TuiLogger) Stack(next logger.Logger) logger.Logger { var eol = []byte("\n") var ansiColorStripper = regexp.MustCompile("\x1b\\[[0-9;]*[mK]") +// Map prefix to severity level +var prefixToLevel = map[string]logger.LogLevel{ + "[TRACE]": logger.LevelTrace, + "[DEBUG]": logger.LevelDebug, + "[INFO]": logger.LevelInfo, + "[WARN]": logger.LevelWarn, + "[ERROR]": logger.LevelError, + "[FATAL]": logger.LevelError, +} + func (l *TuiLogger) Write(p []byte) (n int, err error) { l.mu.Lock() defer l.mu.Unlock() @@ -134,30 +144,14 @@ func (l *TuiLogger) Write(p []byte) (n int, err error) { prefix = strings.TrimSpace(log[:bracket+2]) log = strings.TrimPrefix(log[bracket+2:], " ") } - if strings.HasPrefix(prefix, "[TRACE]") { - severity = logger.LevelTrace - if logger.LevelTrace < l.logLevel { - continue - } - } else if strings.HasPrefix(prefix, "[DEBUG]") { - severity = logger.LevelDebug - if logger.LevelDebug < l.logLevel { - continue - } - } else if strings.HasPrefix(prefix, "[INFO]") { - severity = logger.LevelInfo - if logger.LevelInfo < l.logLevel { - continue - } - } else if strings.HasPrefix(prefix, "[WARN]") { - severity = logger.LevelWarn - if logger.LevelWarn < l.logLevel { - continue - } - } else if strings.HasPrefix(prefix, "[ERROR]") { - severity = logger.LevelError - if logger.LevelError < l.logLevel { - continue + // Find matching prefix + for p, level := range prefixToLevel { + if strings.HasPrefix(prefix, p) { + severity = level + if level < l.logLevel { + continue + } + break } } } From acd8961c6a1ca3938e6ce797d26230226f4fa7df Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Wed, 21 May 2025 12:23:01 -0500 Subject: [PATCH 6/6] For nodejs, we need to use linked sourcemaps --- internal/bundler/bundler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index 70892925..cfb4a012 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -165,7 +165,7 @@ func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject * Outdir: outdir, Write: true, Splitting: false, - Sourcemap: api.SourceMapExternal, + Sourcemap: api.SourceMapLinked, SourcesContent: api.SourcesContentInclude, Format: api.FormatESModule, Platform: api.PlatformNode,