mirror of
https://github.com/chenasraf/watchr.git
synced 2026-05-18 01:29:05 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67de2606cd | ||
| c09d3e41dd | |||
| b8be28d92b | |||
| 69f8ed1ef0 | |||
|
|
d130becd99 | ||
| 5703a61ddb |
20
.github/workflows/manual-homebrew-release.yml
vendored
20
.github/workflows/manual-homebrew-release.yml
vendored
@@ -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 \
|
||||
|
||||
118
.github/workflows/release.yml
vendored
118
.github/workflows/release.yml
vendored
@@ -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 }}
|
||||
|
||||
14
CHANGELOG.md
14
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
||||
|
||||
1
Makefile
1
Makefile
@@ -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"; \
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.5.0
|
||||
1.5.2
|
||||
|
||||
Reference in New Issue
Block a user