From da8bb2e1a2e964bf1ccfafaf68f6557e4ffe27ba Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Fri, 5 Jun 2026 14:21:25 +0800 Subject: [PATCH] style(tools): unify console output with [I/W/E] [Module] color-coded format - Add ANSI color coding: [I]=green, [W]=yellow, [E]=red - Format: [Level] [Module] message with 4-char indent for data rows - Error detail lines use |-> tree connector - Remove extra blank lines between outputs (except list) - Modules: CLI, Preview, Builder, Server, Watcher Co-Authored-By: Claude Opus 4.8 --- tools/cli/create.go | 4 ++-- tools/cli/delete.go | 4 ++-- tools/cli/dev.go | 10 +++++----- tools/cli/list.go | 4 ++-- tools/cli/preview.go | 3 +-- tools/preview/engine.go | 20 +++++++++----------- tools/server/server.mjs | 39 ++++++++++++++++++++++++--------------- tools/tools.go | 2 +- 8 files changed, 46 insertions(+), 40 deletions(-) diff --git a/tools/cli/create.go b/tools/cli/create.go index 79262b1..8daf0c0 100644 --- a/tools/cli/create.go +++ b/tools/cli/create.go @@ -49,7 +49,7 @@ func runCreate(c *cli.Context) error { if info, err := os.Stat(styleDir); err == nil { if info.IsDir() { - fmt.Printf(" [skip] '%s' already exists\n", styleName) + fmt.Printf("\033[32m[I]\033[0m [CLI] '%s' already exists, skipped\n", styleName) continue } return fmt.Errorf("'%s' exists but is not a directory", styleName) @@ -79,7 +79,7 @@ func runCreate(c *cli.Context) error { created++ } - fmt.Printf(" Created style '%s' with %d template files\n", styleName, created) + fmt.Printf("\033[32m[I]\033[0m [CLI] Created style '%s' with %d template files\n", styleName, created) } return nil diff --git a/tools/cli/delete.go b/tools/cli/delete.go index e859e15..6443596 100644 --- a/tools/cli/delete.go +++ b/tools/cli/delete.go @@ -37,14 +37,14 @@ func runDelete(c *cli.Context) error { styleDir := filepath.Join(folder, styleName) if _, err := os.Stat(styleDir); os.IsNotExist(err) { - fmt.Printf(" [skip] '%s' does not exist\n", styleName) + fmt.Printf("\033[33m[W]\033[0m [CLI] '%s' does not exist, skipped\n", styleName) continue } if err := os.RemoveAll(styleDir); err != nil { return fmt.Errorf("cannot delete %s: %w", styleDir, err) } - fmt.Printf(" Deleted style '%s'\n", styleName) + fmt.Printf("\033[32m[I]\033[0m [CLI] Deleted style '%s'\n", styleName) } return nil diff --git a/tools/cli/dev.go b/tools/cli/dev.go index 999c54f..a3ced9f 100644 --- a/tools/cli/dev.go +++ b/tools/cli/dev.go @@ -48,7 +48,7 @@ func runDev(c *cli.Context) error { // Verify node_modules are installed if _, err := os.Stat(filepath.Join(serverDir, "node_modules")); os.IsNotExist(err) { - fmt.Println("Installing Node.js dependencies...") + fmt.Println("\033[32m[I]\033[0m [Builder] Installing Node.js dependencies...") cmd := exec.Command("npm", "install") cmd.Dir = serverDir cmd.Stdout = os.Stdout @@ -64,7 +64,7 @@ func runDev(c *cli.Context) error { initCmd.Stdout = os.Stdout initCmd.Stderr = os.Stderr if err := initCmd.Run(); err != nil { - fmt.Println("Warning: initial preview generation failed, starting anyway...") + fmt.Println("\033[33m[W]\033[0m [Builder] Initial preview generation failed, starting anyway") } // Start the Node.js dev server @@ -78,8 +78,8 @@ func runDev(c *cli.Context) error { return fmt.Errorf("failed to start dev server: %w", err) } - fmt.Printf("\nDev server running at http://localhost:%d\n", port) - fmt.Print("Press Ctrl+C to stop.\n\n") + fmt.Printf("\033[32m[I]\033[0m [Server] Dev server running at http://localhost:%d\n", port) + fmt.Print("\033[32m[I]\033[0m [Server] Press Ctrl+C to stop\n") // Handle graceful shutdown sigCh := make(chan os.Signal, 1) @@ -87,7 +87,7 @@ func runDev(c *cli.Context) error { go func() { <-sigCh - fmt.Println("\nShutting down...") + fmt.Println("\033[32m[I]\033[0m [Server] Shutting down") if err := serverCmd.Process.Signal(os.Interrupt); err != nil { serverCmd.Process.Kill() } diff --git a/tools/cli/list.go b/tools/cli/list.go index 2175d7f..df2c7d9 100644 --- a/tools/cli/list.go +++ b/tools/cli/list.go @@ -37,7 +37,7 @@ func runList(c *cli.Context) error { } absDir, _ := filepath.Abs(folder) - fmt.Printf("Available styles in '%s' (%d):\n", filepath.Base(absDir), len(themes)) + fmt.Printf("\033[32m[I]\033[0m [Preview] Available styles in '%s' (%d):\n", filepath.Base(absDir), len(themes)) sort.Strings(themes) for _, t := range themes { @@ -51,7 +51,7 @@ func runList(c *cli.Context) error { } return nil }) - fmt.Printf(" %-16s (%d .tmpl files)\n", t, count) + fmt.Printf("\033[32m[I]\033[0m [Preview] %-16s (%d .tmpl files)\n", t, count) } return nil } diff --git a/tools/cli/preview.go b/tools/cli/preview.go index 7237d96..0ebcd76 100644 --- a/tools/cli/preview.go +++ b/tools/cli/preview.go @@ -2,7 +2,6 @@ package cli import ( "fmt" - "log" "path/filepath" "github.com/urfave/cli/v2" @@ -78,7 +77,7 @@ func runPreview(c *cli.Context) error { preview.PrintDetailedSummary(result, folder, cfg) - log.Printf("Wrote %d themes x %d templates to %s", + fmt.Printf("\033[32m[I]\033[0m [Preview] Wrote %d themes x %d templates to %s\n", len(result.Summaries), len(cfg.Templates), outputPath) return nil diff --git a/tools/preview/engine.go b/tools/preview/engine.go index 12e08e4..e927115 100644 --- a/tools/preview/engine.go +++ b/tools/preview/engine.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "html/template" - "log" "os" "path/filepath" "strings" @@ -67,7 +66,7 @@ func DiscoverThemes(themesDir string) ([]string, error) { func RenderAll(themesDir string, cfg *config.TemplatesConfig, themeFilter map[string]bool) *PreviewResult { themes, err := DiscoverThemes(themesDir) if err != nil { - log.Printf("ERROR: %v", err) + fmt.Fprintf(os.Stderr, "\033[31m[E]\033[0m [Preview] Error discovering themes: %v\n", err) return nil } @@ -81,7 +80,7 @@ func RenderAll(themesDir string, cfg *config.TemplatesConfig, themeFilter map[st } themes = filtered if len(themes) == 0 { - log.Printf("WARN: no themes matched the requested filter") + fmt.Println("\033[33m[W]\033[0m [Preview] No themes matched the requested filter") return nil } } @@ -249,36 +248,35 @@ func WriteRenderedJS(result *PreviewResult, outputPath string) error { // PrintDetailedSummary prints a human-readable summary of the preview results. func PrintDetailedSummary(result *PreviewResult, themesDir string, cfg *config.TemplatesConfig) { absDir, _ := filepath.Abs(themesDir) - fmt.Printf("\nfound %d styles of possible Gitea mail template in '%s' folder:\n\n", + fmt.Printf("\033[32m[I]\033[0m [Preview] Found %d styles in '%s':\n", len(result.Summaries), filepath.Base(absDir)) for _, s := range result.Summaries { if s.MissingCount == 0 && s.ErrorCount == 0 { - fmt.Printf(" %-16s all required mail templates are properly rendered, (total %d .tmpl files, %s after, %v)\n", + fmt.Printf("\033[32m[I]\033[0m [Preview] %-16s all required templates OK (total %d .tmpl, %s, %v)\n", s.Name, s.TotalFiles, formatSize(s.RenderedSize), s.RenderDuration.Round(time.Millisecond)) } else { - fmt.Printf(" %-16s only %d required mail templates are properly rendered:\n", + fmt.Printf("\033[33m[W]\033[0m [Preview] %-16s only %d required templates rendered:\n", s.Name, s.RenderedCount) for _, tplID := range s.Missing { if t, ok := cfg.Templates[tplID]; ok { - fmt.Printf(" [%s] not found, expected at 'themes/%s/%s'\n", + fmt.Printf("\033[31m[E]\033[0m [Preview] |-> %s not found, expected at 'themes/%s/%s'\n", filepath.Base(t.PathStr()), s.Name, t.PathStr()) } else { - fmt.Printf(" [%s] not found\n", tplID) + fmt.Printf("\033[31m[E]\033[0m [Preview] |-> %s not found\n", tplID) } } for _, tplID := range s.Errors { if rr, ok := s.Results[tplID]; ok && rr.Error != "" { - fmt.Printf(" [%s] render error: %s\n", tplID, rr.Error) + fmt.Printf("\033[31m[E]\033[0m [Preview] |-> %s render error: %s\n", tplID, rr.Error) } else { - fmt.Printf(" [%s] render error\n", tplID) + fmt.Printf("\033[31m[E]\033[0m [Preview] |-> %s render error\n", tplID) } } } } - fmt.Println() } // markSafeHTML recursively converts known HTML-containing string fields to diff --git a/tools/server/server.mjs b/tools/server/server.mjs index 8d10c69..5a81be5 100644 --- a/tools/server/server.mjs +++ b/tools/server/server.mjs @@ -8,6 +8,15 @@ import expressWs from 'express-ws'; import { inlineCSS, stripGmail, stripOutlook } from './inliner.mjs'; +// ANSI color helpers +const G = '\x1b[32m'; // green (Info) +const Y = '\x1b[33m'; // yellow (Warn) +const R = '\x1b[31m'; // red (Error) +const Z = '\x1b[0m'; // reset +const I = `${G}[I]${Z}`; +const W = `${Y}[W]${Z}`; +const E = `${R}[E]${Z}`; + const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = resolve(__dirname, '..', '..'); // project root const TOOLS = join(ROOT, 'tools'); // tools/ (for go run) @@ -55,13 +64,13 @@ async function rebuild() { CHANGED_THEMES.clear(); const start = Date.now(); - console.log(`\n[rebuild] ${themes.length > 0 ? themes.join(', ') : 'all'} changed`); + console.log(`${I} [Builder] Rebuilding: ${themes.length > 0 ? themes.join(', ') : 'all'} changed`); // Step 1: Go render try { await goPreview(themes.length > 0 ? themes : ['all']); } catch (err) { - console.error('[rebuild] go render failed:', err.message); + console.error(`${E} [Builder] Go render failed: ${err.message}`); building = false; broadcast('error', { message: err.message }); return; @@ -73,11 +82,11 @@ async function rebuild() { const juiced = await juiceRenderedOutput(renderedJS); writeFileSync(RENDERED_JS, juiced, 'utf8'); } catch (err) { - console.error('[rebuild] juice step failed:', err.message); + console.error(`${E} [Builder] Juice step failed: ${err.message}`); } const elapsed = Date.now() - start; - console.log(`[rebuild] done in ${elapsed}ms`); + console.log(`${I} [Builder] Rebuild done in ${elapsed}ms`); building = false; @@ -190,19 +199,19 @@ try { const fullPath = join(THEMES, fname); // event is 'rename' for both create and delete; 'change' for modifications if (event === 'change') { - console.log('[watch] ' + relPath(fullPath) + ' edited'); + console.log(`${I} [Watcher] ${relPath(fullPath)} changed`); scheduleRebuild(fullPath); } else if (event === 'rename') { if (existsSync(fullPath)) { - console.log('[watch] ' + relPath(fullPath) + ' created'); + console.log(`${I} [Watcher] ${relPath(fullPath)} created`); } else { - console.log('[watch] ' + relPath(fullPath) + ' deleted'); + console.log(`${I} [Watcher] ${relPath(fullPath)} deleted`); } } }); - console.log('[watch] themes/ (recursive)'); + console.log(`${I} [Watcher] Watching themes/ (recursive)`); } catch (err) { - console.error('[watch] failed:', err.message); + console.error(`${E} [Watcher] File watch failed: ${err.message}`); } // Initial Juice pass @@ -213,9 +222,9 @@ try { const js = readFileSync(RENDERED_JS, 'utf8'); const juiced = await juiceRenderedOutput(js); writeFileSync(RENDERED_JS, juiced, 'utf8'); - console.log('[init] juice inlining applied'); + console.log(`${I} [Builder] CSS inlining applied`); } catch (err) { - console.error('[init] juice failed:', err.message); + console.error(`${E} [Builder] CSS inlining failed: ${err.message}`); } })(); @@ -223,8 +232,8 @@ try { app.get('/health', (_req, res) => res.json({ status: 'ok', port: PORT })); app.listen(PORT, () => { - console.log(`\n Gitea Mail Templates — Dev Server`); - console.log(` http://localhost:${PORT}\n`); - console.log(` Watching themes/ for changes...`); - console.log(` WebSocket ws://localhost:${PORT}/ws`); + console.log(`${I} [Server] Gitea Mail Templates — Dev Server`); + console.log(`${I} [Server] http://localhost:${PORT}`); + console.log(`${I} [Watcher] Watching themes/ for changes`); + console.log(`${I} [Server] WebSocket ws://localhost:${PORT}/ws\n`); }); diff --git a/tools/tools.go b/tools/tools.go index 3deed69..accdd6c 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -24,7 +24,7 @@ func main() { } if err := app.Run(os.Args); err != nil { - fmt.Fprintf(os.Stderr, "error: %v\n", err) + fmt.Fprintf(os.Stderr, "\033[31m[E]\033[0m [CLI] %v\n", err) os.Exit(1) } }