Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion cmd/rune/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"flag"
"fmt"
"io"
"os"

"github.com/CryptoLabInc/rune-cli/internal/bootstrap"
)
Expand All @@ -20,8 +21,21 @@ func runInstall(ctx context.Context, args []string, stdout, stderr io.Writer) in
return 2
}

// Check RUNE_MANIFEST before fail
if *manifest == "" {
fmt.Fprintln(stderr, "rune install: no manifest URL configured (set --manifest-url or RUNE_MANIFEST)")
if env := os.Getenv("RUNE_MANIFEST"); env != "" {
*manifest = env
}
}

if *manifest == "" {
const msg = "no manifest URL configured (set --manifest-url or RUNE_MANIFEST)"
if *jsonOut {
_ = json.NewEncoder(stdout).Encode(jsonEvent{Event: "summary", Error: msg})
} else {
fmt.Fprintln(stderr, "rune install: "+msg)
}

return 2
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/rune/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func main() {
case "install":
os.Exit(runInstall(ctx, args, os.Stdout, os.Stderr))
case "verify":
os.Exit(runVerify(ctx, args, os.Stdout))
os.Exit(runVerify(ctx, args, os.Stdout, os.Stderr))
case "version":
os.Exit(runVersion(os.Stdout))
case "mcp-server":
Expand Down
95 changes: 91 additions & 4 deletions cmd/rune/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func TestRunVersion_EmptyManifest(t *testing.T) {
saved := manifestURL
manifestURL = ""
defer func() { manifestURL = saved }()
t.Setenv("RUNE_MANIFEST", "")

var buf bytes.Buffer
_ = runVersion(&buf)
Expand Down Expand Up @@ -90,8 +91,8 @@ func TestRunVerify_ExitCodeOnFail(t *testing.T) {
t.Setenv("RUNED_HOME", filepath.Join(dir, "runed"))

// No config file
var buf bytes.Buffer
code := runVerify(context.Background(), nil, &buf)
var buf, errBuf bytes.Buffer
code := runVerify(context.Background(), nil, &buf, &errBuf)
if code != 1 {
t.Errorf("exit = %d, want 1 (fail)", code)
}
Expand All @@ -105,8 +106,8 @@ func TestRunVerify_JSONValidity(t *testing.T) {
t.Setenv("RUNE_HOME", filepath.Join(dir, "rune"))
t.Setenv("RUNED_HOME", filepath.Join(dir, "runed"))

var buf bytes.Buffer
_ = runVerify(context.Background(), []string{"--json"}, &buf)
var buf, errBuf bytes.Buffer
_ = runVerify(context.Background(), []string{"--json"}, &buf, &errBuf)
var got bootstrap.InstallChecks
if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
t.Fatalf("output not valid JSON: %v\n%s", err, buf.String())
Expand All @@ -120,6 +121,7 @@ func TestRunInstall_ErrorsWithoutManifest(t *testing.T) {
saved := manifestURL
manifestURL = ""
defer func() { manifestURL = saved }()
t.Setenv("RUNE_MANIFEST", "")

var stdout, stderr bytes.Buffer
code := runInstall(context.Background(), nil, &stdout, &stderr)
Expand All @@ -138,3 +140,88 @@ func TestRunInstall_UnknownFlag(t *testing.T) {
t.Errorf("exit = %d, want 2 (flag parse error)", code)
}
}

func TestRunInstall_CheckManifestEnv(t *testing.T) {
saved := manifestURL
manifestURL = ""
defer func() { manifestURL = saved }()

dir := t.TempDir()
t.Setenv("RUNE_HOME", filepath.Join(dir, "rune"))
t.Setenv("RUNED_HOME", filepath.Join(dir, "runed"))
t.Setenv("RUNE_MANIFEST", "https://example.invalid/manifest.json")

ctx, cancel := context.WithCancel(context.Background())
cancel()

var stdout, stderr bytes.Buffer
code := runInstall(ctx, nil, &stdout, &stderr)
if code == 2 {
t.Errorf("exit = 2 (guard tripped); RUNE_MANIFEST exist but ignored. stderr=%q", stderr.String())
}
if strings.Contains(stderr.String(), "no manifest URL configured") {
t.Errorf("guard message present despite RUNE_MANIFEST set: %q", stderr.String())
}
}

func TestRunInstall_JSONMissingManifest(t *testing.T) {
saved := manifestURL
manifestURL = ""
defer func() { manifestURL = saved }()
t.Setenv("RUNE_MANIFEST", "")

var stdout, stderr bytes.Buffer
code := runInstall(context.Background(), []string{"--json"}, &stdout, &stderr)
if code != 2 {
t.Errorf("exit = %d, want 2", code)
}

// Emit stdout as JSON
var ev jsonEvent
if err := json.Unmarshal(stdout.Bytes(), &ev); err != nil {
t.Fatalf("stdout should be a JSON event; got %q (err %v)", stdout.String(), err)
}

if ev.Event != "summary" {
t.Errorf("event = %q, want \"summary\"", ev.Event)
}
if ev.Error == "" {
t.Errorf("missing-manifest summary must carry an error; got %+v", ev)
}
}

func TestRunVersion_CheckManifestEnv(t *testing.T) {
saved := manifestURL
manifestURL = ""
defer func() { manifestURL = saved }()

t.Setenv("RUNE_MANIFEST", "https://example.invalid/m.json")

var buf bytes.Buffer
if code := runVersion(&buf); code != 0 {
t.Errorf("exit = %d, want 0", code)
}

out := buf.String()
if !strings.Contains(out, "https://example.invalid/m.json") {
t.Errorf("RUNE_MANIFEST exist but ignored: %q", out)
}
if strings.Contains(out, "manifest missing") {
t.Errorf("should not report missing when RUNE_MANIFEST is set: %q", out)
}
}

func TestRunVerify_BadFlagToStderr(t *testing.T) {
var stdout, stderr bytes.Buffer
code := runVerify(context.Background(), []string{"--no-such-flag"}, &stdout, &stderr)
if code != 2 {
t.Errorf("exit = %d, want 2 (flag parse error)", code)
}

if stdout.Len() != 0 {
t.Errorf("stdout must clean for --json consumers; got %q", stdout.String())
}
if !strings.Contains(stderr.String(), "not defined") {
t.Errorf("flag error should stderr; got %q", stderr.String())
}
}
4 changes: 2 additions & 2 deletions cmd/rune/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (
"github.com/CryptoLabInc/rune-cli/internal/bootstrap"
)

func runVerify(ctx context.Context, args []string, stdout io.Writer) int {
func runVerify(ctx context.Context, args []string, stdout, stderr io.Writer) int {
fs := flag.NewFlagSet("verify", flag.ContinueOnError)
fs.SetOutput(stdout)
fs.SetOutput(stderr)
jsonOut := fs.Bool("json", false, "emit JSON instead of human-readable text")
if err := fs.Parse(args); err != nil {
return 2
Expand Down
10 changes: 8 additions & 2 deletions cmd/rune/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ package main
import (
"fmt"
"io"
"os"
)

func runVersion(w io.Writer) int {
fmt.Fprintf(w, "rune %s\n", runeVersion)
if manifestURL != "" {
fmt.Fprintf(w, "manifest: %s\n", manifestURL)
manifest := manifestURL
if manifest == "" {
manifest = os.Getenv("RUNE_MANIFEST")
}

if manifest != "" {
fmt.Fprintf(w, "manifest: %s\n", manifest)
} else {
fmt.Fprintln(w, "manifest missing: supply --manifest-url or RUNE_MANIFEST")
}
Expand Down