utils
API
utils
packageAPI reference for the utils
package.
Imports
(18)
F
function
TestOpenBrowserError
Parameters
t
cmd/rfw/utils/network_test.go:8-16
func TestOpenBrowserError(t *testing.T)
{
orig := os.Getenv("BROWSER")
_ = os.Setenv("BROWSER", "nonexistent-browser")
defer os.Setenv("BROWSER", orig)
if err := OpenBrowser("http://example.com"); err == nil {
t.Fatalf("expected error when browser command is missing")
}
}
F
function
ClearScreen
cmd/rfw/utils/output.go:26-29
func ClearScreen()
{
fmt.Print("\033[H\033[2J")
fmt.Println()
}
F
function
PrintStartupInfo
Parameters
port
string
httpsPort
string
localIP
string
host
bool
cmd/rfw/utils/output.go:31-44
func PrintStartupInfo(port, httpsPort, localIP string, host bool)
{
fmt.Println(indent, boldRed("rfw"), faint(core.Version))
fmt.Println()
fmt.Println(indent, red("➜ "), bold("Local:"), red(fmt.Sprintf("http://localhost:%s/ - https://localhost:%s/", port, httpsPort)))
if host {
fmt.Println(indent, red("➜ "), faint(bold("Network:")), white(fmt.Sprintf("http://%s:%s/ (https://%s:%s/)", localIP, port, localIP, httpsPort)))
} else {
fmt.Println(indent, red("➜ "), faint(bold("Network:")), white("--host"), faint("to expose"))
}
fmt.Println(indent, faintRed("➜ "), faint("Press"), bold("h + enter"), faint("to show help"))
fmt.Println()
}
F
function
LogServeRequest
Parameters
r
cmd/rfw/utils/output.go:46-48
func LogServeRequest(r *http.Request)
{
fmt.Printf("%s %s %s\n", faint(time.Now().Format("15:04:05")), boldYellow("serving"), faint(r.URL.Path))
}
F
function
Info
Parameters
message
string
cmd/rfw/utils/output.go:50-52
func Info(message string)
{
fmt.Println(boldRed("[rfw]"), message)
}
F
function
Fatal
Parameters
message
string
err
error
cmd/rfw/utils/output.go:54-56
func Fatal(message string, err error)
{
log.Fatalf(boldRed("[rfw] "), message, err)
}
F
function
EnableDebug
Parameters
d
bool
cmd/rfw/utils/output.go:58-58
func EnableDebug(d bool)
{ dbg = d }
F
function
IsDebug
IsDebug reports whether debug mode is enabled.
Returns
bool
cmd/rfw/utils/output.go:61-61
func IsDebug() bool
{ return dbg }
F
function
Debug
Parameters
message
string
cmd/rfw/utils/output.go:63-67
func Debug(message string)
{
if dbg {
fmt.Println(boldRed("[rfw][debug]"), faint(message))
}
}
F
function
PrintHelp
cmd/rfw/utils/output.go:69-82
func PrintHelp()
{
ClearScreen()
fmt.Println()
fmt.Println(indent, red("➜ "), bold("Help"))
fmt.Println(indent, indent, yellow("➜ "), bold("Shortcuts"))
fmt.Println(indent, indent, indent, faint("Press"), bold("c + enter"), faint("to stop the server"))
fmt.Println(indent, indent, indent, faint("Press"), bold("o + enter"), faint("to open the browser"))
fmt.Println(indent, indent, indent, faint("Press"), bold("u + enter"), faint("to show the startup info and clear logs"))
fmt.Println(indent, indent, indent, faint("Press"), bold("h + enter"), faint("to show this help"))
fmt.Println(indent, indent, yellow("➜ "), bold("Flags"))
fmt.Println(indent, indent, indent, faint("Use"), bold("--host"), faint("to expose the server to the network"))
fmt.Println(indent, indent, indent, faint("Use"), bold("--port=XXXX"), faint("to specify a port"))
fmt.Println()
}
S
struct
githubRelease
cmd/rfw/utils/update.go:27-33
type githubRelease struct
Fields
| Name | Type | Description |
|---|---|---|
| TagName | string | json:"tag_name" |
| Assets | []struct { BrowserDownloadURL string `json:"browser_download_url"` Name string `json:"name"` } | json:"assets" |
F
function
fetchLatestVersion
Returns
string
error
cmd/rfw/utils/update.go:35-70
func fetchLatestVersion() (string, error)
{
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.github.com/repos/"+githubRepo+"/releases/latest", nil)
if err != nil {
return "", err
}
req.Header.Set("Accept", "application/vnd.github+json")
client := &http.Client{Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return net.DialTimeout(network, addr, 3*time.Second)
},
}}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", fmt.Errorf("status %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var release githubRelease
if err := json.Unmarshal(body, &release); err != nil {
return "", err
}
return release.TagName, nil
}
F
function
shouldCheckUpdate
Returns
bool
cmd/rfw/utils/update.go:72-83
func shouldCheckUpdate() bool
{
home, err := os.UserHomeDir()
if err != nil {
return true
}
path := home + "/" + checkFile
info, err := os.Stat(path)
if err != nil {
return true
}
return time.Since(info.ModTime()) > checkInterval
}
F
function
markChecked
cmd/rfw/utils/update.go:85-92
func markChecked()
{
home, err := os.UserHomeDir()
if err != nil {
return
}
path := home + "/" + checkFile
os.WriteFile(path, []byte(time.Now().Format(time.RFC3339)), 0644)
}
F
function
isNewer
Parameters
current
string
latest
string
Returns
bool
cmd/rfw/utils/update.go:94-114
func isNewer(current, latest string) bool
{
c := strings.TrimPrefix(current, "v")
l := strings.TrimPrefix(latest, "v")
if c == "" || l == "" {
return false
}
partsC := strings.SplitN(c, ".", 3)
partsL := strings.SplitN(l, ".", 3)
for i := 0; i < 3; i++ {
if i >= len(partsC) || i >= len(partsL) {
break
}
if partsL[i] > partsC[i] {
return true
}
if partsL[i] < partsC[i] {
return false
}
}
return false
}
F
function
downloadAndReplace
Parameters
assetURL
string
Returns
error
cmd/rfw/utils/update.go:116-160
func downloadAndReplace(assetURL string) error
{
tmp, err := os.CreateTemp("", "rfw-update-*")
if err != nil {
return err
}
tmp.Close()
defer os.Remove(tmp.Name())
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", assetURL, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("download failed: status %d", resp.StatusCode)
}
if _, err := io.Copy(tmp, resp.Body); err != nil {
return err
}
exePath, err := os.Executable()
if err != nil {
return err
}
if err := os.Chmod(tmp.Name(), 0755); err != nil {
return err
}
if err := os.Rename(tmp.Name(), exePath); err != nil {
return os.WriteFile(exePath, mustReadFile(tmp.Name()), 0755)
}
return nil
}
F
function
mustReadFile
Parameters
path
string
Returns
[]byte
cmd/rfw/utils/update.go:162-170
func mustReadFile(path string) []byte
{
f, err := os.Open(path)
if err != nil {
return nil
}
defer f.Close()
data, _ := io.ReadAll(f)
return data
}
F
function
getAssetName
Returns
string
cmd/rfw/utils/update.go:172-180
func getAssetName() string
{
goos := runtime.GOOS
goarch := runtime.GOARCH
ext := ""
if goos == "windows" {
ext = ".exe"
}
return fmt.Sprintf("rfw-%s-%s%s", goos, goarch, ext)
}
F
function
CheckForUpdate
cmd/rfw/utils/update.go:182-244
func CheckForUpdate()
{
if !shouldCheckUpdate() {
return
}
latest, err := fetchLatestVersion()
if err != nil {
return
}
markChecked()
if !isNewer(core.Version, latest) {
return
}
assetName := getAssetName()
fmt.Println()
Info(fmt.Sprintf("Update available: %s → %s", faint(core.Version), boldCyan(latest)))
fmt.Print(indent, red("➜ "), bold("Update now? [y/N] "))
var answer string
fmt.Scanln(&answer)
answer = strings.TrimSpace(strings.ToLower(answer))
if answer != "y" && answer != "yes" {
fmt.Println(indent, faint("Skipped."))
return
}
assetURL := ""
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.github.com/repos/"+githubRepo+"/releases/latest", nil)
req.Header.Set("Accept", "application/vnd.github+json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
Info("Failed to fetch release info")
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var release githubRelease
json.Unmarshal(body, &release)
for _, a := range release.Assets {
if a.Name == assetName {
assetURL = a.BrowserDownloadURL
break
}
}
if assetURL == "" {
Info(fmt.Sprintf("No binary found for %s/%s", runtime.GOOS, runtime.GOARCH))
return
}
Info("Downloading...")
if err := downloadAndReplace(assetURL); err != nil {
Info(fmt.Sprintf("Update failed: %v", err))
return
}
Info(fmt.Sprintf("Updated to %s!", latest))
}
F
function
captureOutput
captureOutput redirects stdout for the duration of f and returns what was
written to it.
Parameters
f
func()
Returns
string
cmd/rfw/utils/utils_test.go:14-24
func captureOutput(f func()) string
{
orig := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
w.Close()
os.Stdout = orig
var buf bytes.Buffer
_, _ = io.Copy(&buf, r)
return buf.String()
}
F
function
TestDebug
Parameters
t
cmd/rfw/utils/utils_test.go:26-38
func TestDebug(t *testing.T)
{
EnableDebug(true)
out := captureOutput(func() { Debug("hello") })
if !strings.Contains(out, "[rfw][debug]") {
t.Fatalf("expected debug output, got %q", out)
}
EnableDebug(false)
out = captureOutput(func() { Debug("no output") })
if out != "" {
t.Fatalf("expected no output, got %q", out)
}
}
F
function
TestIsDebug
Parameters
t
cmd/rfw/utils/utils_test.go:40-49
func TestIsDebug(t *testing.T)
{
EnableDebug(true)
if !IsDebug() {
t.Fatalf("expected true in debug mode")
}
EnableDebug(false)
if IsDebug() {
t.Fatalf("expected false when debug disabled")
}
}
F
function
TestPrintStartupInfo
Parameters
t
cmd/rfw/utils/utils_test.go:51-63
func TestPrintStartupInfo(t *testing.T)
{
out := captureOutput(func() { PrintStartupInfo("8080", "8443", "192.168.0.1", true) })
if !strings.Contains(out, "http://localhost:8080/") {
t.Fatalf("expected local URL in output, got %q", out)
}
if !strings.Contains(out, "http://192.168.0.1:8080/") {
t.Fatalf("expected network URL, got %q", out)
}
out = captureOutput(func() { PrintStartupInfo("8080", "8443", "", false) })
if !strings.Contains(out, "--host") {
t.Fatalf("expected hint about --host, got %q", out)
}
}
F
function
TestPrintHelp
Parameters
t
cmd/rfw/utils/utils_test.go:65-70
func TestPrintHelp(t *testing.T)
{
out := captureOutput(PrintHelp)
if !strings.Contains(out, "Shortcuts") || !strings.Contains(out, "Flags") {
t.Fatalf("missing help sections, got %q", out)
}
}
F
function
TestLogServeRequest
Parameters
t
cmd/rfw/utils/utils_test.go:72-78
func TestLogServeRequest(t *testing.T)
{
req := httptest.NewRequest("GET", "/foo", nil)
out := captureOutput(func() { LogServeRequest(req) })
if !strings.Contains(out, "/foo") {
t.Fatalf("expected path in output, got %q", out)
}
}
F
function
GetLocalIP
Returns
string
error
cmd/rfw/utils/network.go:13-26
func GetLocalIP() (string, error)
{
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
for _, addr := range addrs {
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil {
return ipNet.IP.String(), nil
}
}
return "", fmt.Errorf("no local IP address found")
}
F
function
OpenBrowser
Parameters
url
string
Returns
error
cmd/rfw/utils/network.go:28-36
func OpenBrowser(url string) error
{
if configured := os.Getenv("BROWSER"); configured != "" {
cmd := strings.Fields(configured)[0]
if _, err := exec.LookPath(cmd); err != nil {
return err
}
}
return browser.OpenURL(url)
}