mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-08 09:49:34 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1febcb8168 | ||
|
|
36d45b96ab |
No files matched your search
@@ -13,6 +13,7 @@ require (
|
||||
github.com/aymanbagabas/go-udiff v0.4.1
|
||||
github.com/charmbracelet/colorprofile v0.4.3
|
||||
github.com/charmbracelet/x/ansi v0.11.7
|
||||
github.com/charmbracelet/x/term v0.2.2
|
||||
github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728
|
||||
golang.org/x/sys v0.46.0
|
||||
mvdan.cc/sh/v3 v3.13.1
|
||||
@@ -21,7 +22,6 @@ require (
|
||||
require (
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20260615092913-2399af76d5b1 // indirect
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20260615092313-b57e5e6d29bb // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
|
||||
@@ -140,6 +140,26 @@ func TestPostinstallSkipsUnsupportedPlatform(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostinstallSkipsWindowsArm64(t *testing.T) {
|
||||
// (win32, arm64) resolves to a valid platform/arch but the release matrix has
|
||||
// no windows-arm64 artifact, so the install must skip gracefully (exit 0)
|
||||
// rather than hard-fail on a 404 download.
|
||||
stdout, stderr, err := runPostinstall(t,
|
||||
"ZERO_INSTALL_DRY_RUN=1",
|
||||
"ZERO_INSTALL_PLATFORM=win32",
|
||||
"ZERO_INSTALL_ARCH=arm64",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("windows-arm64 should exit 0, got err=%v stderr=%s", err, stderr)
|
||||
}
|
||||
if strings.TrimSpace(stdout) != "" {
|
||||
t.Fatalf("windows-arm64 should not print a plan, got %q", stdout)
|
||||
}
|
||||
if !strings.Contains(stderr, "no prebuilt binary for windows-arm64") {
|
||||
t.Fatalf("stderr=%q, want the windows-arm64 skip message", stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostinstallHonorsSkipEnv(t *testing.T) {
|
||||
stdout, stderr, err := runPostinstall(t, "ZERO_SKIP_DOWNLOAD=1")
|
||||
if err != nil {
|
||||
|
||||
@@ -7,10 +7,22 @@ import (
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"github.com/charmbracelet/colorprofile"
|
||||
"github.com/charmbracelet/x/term"
|
||||
)
|
||||
|
||||
// Run starts the Zero Bubble Tea shell and returns a process-style exit code.
|
||||
func Run(ctx context.Context, options Options) int {
|
||||
// The interactive shell needs a real terminal on stdin: with piped or
|
||||
// redirected input Bubble Tea blocks forever waiting for events that never
|
||||
// arrive (e.g. `echo "" | zero`). Fail fast with guidance toward the headless
|
||||
// path instead of hanging. term.IsTerminal is a true TTY check (it rejects
|
||||
// pipes, regular files, and non-terminal char devices like /dev/null) and
|
||||
// fails closed — anything that is not a verified terminal blocks the shell.
|
||||
if !term.IsTerminal(os.Stdin.Fd()) {
|
||||
fmt.Fprintln(os.Stderr, "zero: the interactive shell needs a terminal (stdin is not a TTY). For non-interactive use, run: zero exec \"<prompt>\"")
|
||||
return 2
|
||||
}
|
||||
|
||||
externalSink := options.RuntimeMessageSink
|
||||
var program *tea.Program
|
||||
options.RuntimeMessageSink = func(msg tea.Msg) {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package tui
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestUseAltScreenForInteractiveChat(t *testing.T) {
|
||||
if !useAltScreen(Options{}) {
|
||||
@@ -10,3 +15,31 @@ func TestUseAltScreenForInteractiveChat(t *testing.T) {
|
||||
t.Fatal("setup takeover should also use the alternate screen")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunRejectsNonTTYStdin pins that the interactive shell fails fast with a
|
||||
// non-zero code when stdin is not a terminal, instead of blocking forever in the
|
||||
// Bubble Tea event loop (e.g. `echo "" | zero`). The guard runs before any model
|
||||
// construction, so empty Options are fine.
|
||||
func TestRunRejectsNonTTYStdin(t *testing.T) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("os.Pipe: %v", err)
|
||||
}
|
||||
w.Close() // a pipe read-end is not a character device
|
||||
|
||||
old := os.Stdin
|
||||
os.Stdin = r
|
||||
defer func() { os.Stdin = old; r.Close() }()
|
||||
|
||||
done := make(chan int, 1)
|
||||
go func() { done <- Run(context.Background(), Options{}) }()
|
||||
|
||||
select {
|
||||
case code := <-done:
|
||||
if code != 2 {
|
||||
t.Fatalf("Run with non-TTY stdin returned %d; want exit code 2 from the stdin TTY guard", code)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("Run blocked on non-TTY stdin instead of failing fast")
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -38,5 +38,5 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/Gitlawb/zero/issues"
|
||||
},
|
||||
"license": "SEE LICENSE IN LICENSE"
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -102,6 +102,19 @@ if (!platform || !arch) {
|
||||
);
|
||||
}
|
||||
|
||||
// The release matrix builds linux/macos {x64,arm64} and windows-x64 — there is no
|
||||
// windows-arm64 artifact. (win32, arm64) otherwise resolves to a valid
|
||||
// platform/arch, so it would proceed to download a non-existent asset and hard
|
||||
// -fail npm install on a 404. Guard it explicitly to skip gracefully; Windows on
|
||||
// ARM runs the x64 build under emulation, so the x64 package is the fallback.
|
||||
if (platform === 'windows' && arch === 'arm64') {
|
||||
warnSkip(
|
||||
`no prebuilt binary for windows-arm64 (the windows-x64 build runs under ` +
|
||||
`emulation on Windows on ARM). Build from source or install the x64 package: ` +
|
||||
`https://github.com/${REPO} (go run ./cmd/zero).`,
|
||||
);
|
||||
}
|
||||
|
||||
const ext = platform === 'windows' ? 'zip' : 'tar.gz';
|
||||
const binaryName = platform === 'windows' ? 'zero.exe' : 'zero';
|
||||
const tag = `v${VERSION}`;
|
||||
|
||||
Reference in new issue
Block a user