6 Commits

Author SHA1 Message Date
github-actions[bot]
67de2606cd chore(master): release 1.5.2 2026-01-24 02:05:28 +02:00
c09d3e41dd fix: tab characters breaking the layout 2026-01-24 02:03:50 +02:00
b8be28d92b build: use reusable go-release workflow 2026-01-22 23:21:49 +02:00
69f8ed1ef0 build: add changelog to homebrew workflows 2026-01-22 21:26:42 +02:00
github-actions[bot]
d130becd99 chore(master): release 1.5.1 2026-01-07 22:57:55 +02:00
5703a61ddb fix: esc button behavior 2025-12-31 11:28:08 +02:00
8 changed files with 120 additions and 119 deletions

View File

@@ -15,12 +15,19 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Get latest tag
- name: Get latest release info
id: latest
run: |
tag=$(gh release view --json tagName -q .tagName)
echo "Latest release tag: $tag"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
# Get release body and escape for JSON
body=$(gh release view --json body -q .body)
# Use delimiter for multiline output
echo "body<<EOF" >> "$GITHUB_OUTPUT"
echo "$body" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -30,7 +37,16 @@ jobs:
run: |
tag="${{ steps.latest.outputs.tag }}"
repo="${{ github.event.repository.name }}"
data="{\"event_type\":\"trigger-from-release\",\"client_payload\":{\"tag\":\"$tag\",\"repo\":\"$repo\"}}"
# Use jq to properly escape the body for JSON
body=$(cat <<'BODY_EOF'
${{ steps.latest.outputs.body }}
BODY_EOF
)
data=$(jq -n \
--arg tag "$tag" \
--arg repo "$repo" \
--arg body "$body" \
'{event_type: "trigger-from-release", client_payload: {tag: $tag, repo: $repo, body: $body}}')
echo "Dispatching tag $tag from $repo"
echo "Data: $data"
curl -X POST \

View File

@@ -11,114 +11,10 @@ permissions:
pull-requests: write
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23'
- name: Test
run: go test -v ./...
generate:
name: Build for ${{ matrix.platform }}
runs-on: ubuntu-latest
strategy:
matrix:
include:
- platform: linux/amd64
label: linux-amd64
- platform: darwin/amd64
label: darwin-amd64
- platform: darwin/arm64
label: darwin-arm64
- platform: windows/amd64
label: windows-amd64
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build for ${{ matrix.label }}
uses: chenasraf/go-cross-build@v1
with:
platforms: ${{ matrix.platform }}
package: ''
name: 'watchr'
compress: 'true'
dest: dist
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: "dist-${{ matrix.label }}"
path: dist
release-please:
name: Release
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
needs:
- test
- generate
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download all builds
uses: actions/download-artifact@v4
with:
path: dist
- name: Verify Release Artifacts
run: |
ls -la dist
for i in "linux-amd64" "darwin-amd64" "windows-amd64" "darwin-arm64"; do
if [[ ! -f ./dist/dist-$i/watchr-$i.tar.gz ]]; then
echo "File not found: ./dist/dist-$i/watchr-$i.tar.gz"
exit 1
fi
done
- name: Run Release Please
uses: googleapis/release-please-action@v4
id: release
with:
release-type: simple
- name: Upload Release Artifacts
if: ${{ steps.release.outputs.release_created }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
for i in "linux-amd64" "darwin-amd64" "darwin-arm64" "windows-amd64"; do
gh release upload "${{ steps.release.outputs.tag_name }}" "./dist/dist-$i/watchr-$i.tar.gz"
done
release-homebrew:
name: Homebrew Release
needs: [release-please]
if: ${{ needs.release-please.outputs.release_created }}
runs-on: ubuntu-latest
steps:
- name: Send dispatch to homebrew-tap
env:
GH_TOKEN: ${{ secrets.REPO_DISPATCH_PAT }}
run: |
repo="${{ github.event.repository.name }}"
tag="${{ needs.release-please.outputs.tag_name }}"
data="{\"event_type\":\"trigger-from-release\",\"client_payload\":{\"tag\":\"$tag\",\"repo\":\"$repo\"}}"
echo "Dispatching tag $tag from $repo"
echo "Data: $data"
curl -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
https://api.github.com/repos/chenasraf/homebrew-tap/dispatches \
-d "$data"
echo "Dispatched tag $tag from $repo"
echo "Created job on https://github.com/chenasraf/homebrew-tap/actions"
release:
uses: chenasraf/workflows/.github/workflows/go-release.yml@master
with:
name: watchr
homebrew-tap-repo: chenasraf/homebrew-tap
secrets:
REPO_DISPATCH_PAT: ${{ secrets.REPO_DISPATCH_PAT }}

View File

@@ -1,5 +1,19 @@
# Changelog
## [1.5.2](https://github.com/chenasraf/watchr/compare/v1.5.1...v1.5.2) (2026-01-24)
### Bug Fixes
* tab characters breaking the layout ([c09d3e4](https://github.com/chenasraf/watchr/commit/c09d3e41ddd69c44ea0546cb0608abe8b0c50334))
## [1.5.1](https://github.com/chenasraf/watchr/compare/v1.5.0...v1.5.1) (2025-12-31)
### Bug Fixes
* esc button behavior ([5703a61](https://github.com/chenasraf/watchr/commit/5703a61ddb3ee25e58470c537d53bf4a463bc632))
## [1.5.0](https://github.com/chenasraf/watchr/compare/v1.4.0...v1.5.0) (2025-12-14)

View File

@@ -48,7 +48,6 @@ precommit:
echo "Running pre-commit checks..."; \
echo "go fmt"; \
go fmt ./...; \
git add $$STAGED_FILES; \
echo "go vet"; \
go vet ./...; \
echo "golangci-lint"; \

View File

@@ -12,6 +12,15 @@ import (
"sync"
)
// sanitizeLine removes control sequences that can corrupt terminal rendering
func sanitizeLine(s string) string {
// Remove carriage returns
s = strings.ReplaceAll(s, "\r", "")
// Convert tabs to spaces (tabs cause width calculation issues)
s = strings.ReplaceAll(s, "\t", " ")
return s
}
// Line represents a single line of output with its line number
type Line struct {
Number int
@@ -141,7 +150,7 @@ func (r *Runner) Run(ctx context.Context) (Result, error) {
for scanner.Scan() {
lines = append(lines, Line{
Number: lineNum,
Content: scanner.Text(),
Content: sanitizeLine(scanner.Text()),
})
lineNum++
}
@@ -151,7 +160,7 @@ func (r *Runner) Run(ctx context.Context) (Result, error) {
for stderrScanner.Scan() {
lines = append(lines, Line{
Number: lineNum,
Content: stderrScanner.Text(),
Content: sanitizeLine(stderrScanner.Text()),
})
lineNum++
}
@@ -265,7 +274,7 @@ func (r *Runner) RunStreaming(ctx context.Context) *StreamingResult {
result.mu.Lock()
*result.Lines = append(*result.Lines, Line{
Number: currentLineNum,
Content: scanner.Text(),
Content: sanitizeLine(scanner.Text()),
})
result.mu.Unlock()
}

View File

@@ -320,3 +320,61 @@ func TestSplitLines(t *testing.T) {
})
}
}
func TestSanitizeLine(t *testing.T) {
tests := []struct {
name string
input string
want string
}{
{
name: "plain text unchanged",
input: "hello world",
want: "hello world",
},
{
name: "tabs converted to spaces",
input: "col1\tcol2\tcol3",
want: "col1 col2 col3",
},
{
name: "carriage returns removed",
input: "line with\r\nwindows ending",
want: "line with\nwindows ending",
},
{
name: "carriage return only removed",
input: "progress\roverwrite",
want: "progressoverwrite",
},
{
name: "ANSI color codes preserved",
input: "\x1b[32mgreen text\x1b[0m",
want: "\x1b[32mgreen text\x1b[0m",
},
{
name: "mixed tabs and colors",
input: "\x1b[1m?\x1b[0m\tpackage\t[no test files]",
want: "\x1b[1m?\x1b[0m package [no test files]",
},
{
name: "empty string",
input: "",
want: "",
},
{
name: "multiple tabs",
input: "\t\t\t",
want: " ",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := sanitizeLine(tt.input)
if got != tt.want {
t.Errorf("sanitizeLine(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}

View File

@@ -295,7 +295,16 @@ func (m *model) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
// Normal mode keybindings
switch msg.String() {
case "q", "esc", "ctrl+c":
case "q", "ctrl+c":
m.cancel()
return m, tea.Quit
case "esc":
// Clear filter if active, otherwise quit
if m.filter != "" {
m.filter = ""
m.updateFiltered()
return m, nil
}
m.cancel()
return m, tea.Quit

View File

@@ -1 +1 @@
1.5.0
1.5.2