From 8dbbe294df6299dc4fab61b6bd4a3f53800c92f2 Mon Sep 17 00:00:00 2001 From: Vincent ibochi <290086463+ibochivincent-lang@users.noreply.github.com> Date: Sun, 30 Aug 2026 15:47:48 +0100 Subject: [PATCH 1/2] feat(cli): scaffold the cli/ Go module for the signet binary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every CLI issue is blocked on this module existing. Add cli/ as a standalone Go module (go 1.25, no cgo) alongside the pnpm workspace, building a signet binary via cobra: cmd/signet/main.go is the entrypoint, internal/cmd holds the command tree, and internal/link, internal/keys, internal/spec are placeholder packages for the wallet-linking, key-management, and API-spec work that follows. signet --version carries a version and commit string injected via ldflags. signet --help and a bare `signet` invocation both print real usage text — cobra only renders the Usage: section when a command is Runnable() or has subcommands, so the root command needed an explicit RunE (falling through to cmd.Help()) to show it before any subcommand exists. golangci-lint is configured via cli/.golangci.yml, and a new CI job builds, vets, tests, and lints the module. Verified go build/go vet/go test all pass, and the binary cross-compiles with CGO_ENABLED=0 for linux/amd64 and darwin/arm64. --- .github/workflows/ci.yml | 28 ++++++++++++++++ README.md | 2 ++ cli/.gitignore | 1 + cli/.golangci.yml | 16 +++++++++ cli/README.md | 52 +++++++++++++++++++++++++++++ cli/cmd/signet/main.go | 28 ++++++++++++++++ cli/go.mod | 11 ++++++ cli/go.sum | 12 +++++++ cli/internal/cmd/root.go | 46 +++++++++++++++++++++++++ cli/internal/cmd/root_test.go | 63 +++++++++++++++++++++++++++++++++++ cli/internal/keys/doc.go | 4 +++ cli/internal/link/doc.go | 4 +++ cli/internal/spec/doc.go | 5 +++ 13 files changed, 272 insertions(+) create mode 100644 cli/.gitignore create mode 100644 cli/.golangci.yml create mode 100644 cli/README.md create mode 100644 cli/cmd/signet/main.go create mode 100644 cli/go.mod create mode 100644 cli/go.sum create mode 100644 cli/internal/cmd/root.go create mode 100644 cli/internal/cmd/root_test.go create mode 100644 cli/internal/keys/doc.go create mode 100644 cli/internal/link/doc.go create mode 100644 cli/internal/spec/doc.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0366238..79ab803 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,6 +58,34 @@ jobs: - name: Build run: pnpm build + cli: + name: signet CLI (Go) + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + working-directory: cli + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-go@v5 + with: + go-version-file: cli/go.mod + + - name: Build + run: go build ./... + + - name: Vet + run: go vet ./... + + - name: Test + run: go test ./... + + - uses: golangci/golangci-lint-action@v6 + with: + version: latest + working-directory: cli + contracts: name: soroban contract tests runs-on: ubuntu-latest diff --git a/README.md b/README.md index 7cd9765..423cea3 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,7 @@ set from the event stream. | `packages/sdk` | External SDK for integrators | | `packages/types` | Shared TypeScript types | | `packages/ui` | Shared React components | +| `cli` | `signet` CLI (Go) — links wallets, manages keys, talks to a Signet deployment | | `infra` | Local dev infra (Docker Postgres) | ## Scripts @@ -184,6 +185,7 @@ set from the event stream. | `pnpm db:migrate` | Run Prisma migrations | | `pnpm test` | All TypeScript tests via Turborepo | | `cargo test` (in `packages/contracts`) | Identity Registry unit tests | +| `go build ./...` / `go test ./...` (in `cli`) | Build / test the `signet` CLI | ## Tests diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/cli/.golangci.yml b/cli/.golangci.yml new file mode 100644 index 0000000..5fbd60a --- /dev/null +++ b/cli/.golangci.yml @@ -0,0 +1,16 @@ +version: "2" + +linters: + default: standard + enable: + - bodyclose + - errcheck + - govet + - ineffassign + - staticcheck + - unused + +formatters: + enable: + - gofmt + - goimports diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 0000000..acf5e70 --- /dev/null +++ b/cli/README.md @@ -0,0 +1,52 @@ +# signet CLI + +The `signet` command-line companion to the Signet developer identity registry +on Stellar/Soroban. It lives beside the pnpm workspace rather than inside it — +this is a standalone Go module, not a pnpm package — mirroring the split +between orchestration/UX (Go, here) and anything that must execute Soroban +semantics (Rust, `packages/contracts`). + +This module is currently a scaffold: the command tree exists (`signet +--help`, `signet --version`), but the actual subcommands (linking a wallet, +managing keys, talking to a deployment) land in follow-up issues. + +## Build + +```bash +cd cli +go build -o bin/signet ./cmd/signet +``` + +To bake a version string and commit hash into the binary: + +```bash +go build \ + -ldflags "-X main.version=$(git describe --tags --always) -X main.commit=$(git rev-parse --short HEAD)" \ + -o bin/signet ./cmd/signet +``` + +## Test / lint + +```bash +go vet ./... +go test ./... +golangci-lint run ./... +``` + +No cgo is used anywhere in this module, so it cross-compiles with the +standard `GOOS`/`GOARCH` combinations, e.g.: + +```bash +GOOS=darwin GOARCH=arm64 go build -o bin/signet-darwin-arm64 ./cmd/signet +GOOS=linux GOARCH=amd64 go build -o bin/signet-linux-amd64 ./cmd/signet +``` + +## Layout + +| Path | Purpose | +|------|---------| +| `cmd/signet` | `main.go` — the binary's entrypoint | +| `internal/cmd` | Cobra command tree | +| `internal/link` | Bind a wallet to a Signet handle (scaffolded, not yet implemented) | +| `internal/keys` | Local signing key management (scaffolded, not yet implemented) | +| `internal/spec` | Typed request/response models for a Signet deployment's HTTP API (scaffolded, not yet implemented) | diff --git a/cli/cmd/signet/main.go b/cli/cmd/signet/main.go new file mode 100644 index 0000000..796e01f --- /dev/null +++ b/cli/cmd/signet/main.go @@ -0,0 +1,28 @@ +// Command signet is the CLI companion to the Signet developer identity +// registry on Stellar/Soroban: it links wallets to on-chain handles, manages +// local signing keys, and talks to a Signet deployment over its HTTP API. +package main + +import ( + "fmt" + "os" + + "github.com/blockchain-maxis/signet/cli/internal/cmd" +) + +// version and commit are overridden at build time via: +// +// go build -ldflags "-X main.version=$(git describe --tags) -X main.commit=$(git rev-parse --short HEAD)" +// +// They default to "dev"/"none" for a plain `go build` or `go run`. +var ( + version = "dev" + commit = "none" +) + +func main() { + if err := cmd.Execute(version, commit); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cli/go.mod b/cli/go.mod new file mode 100644 index 0000000..07575ab --- /dev/null +++ b/cli/go.mod @@ -0,0 +1,11 @@ +module github.com/blockchain-maxis/signet/cli + +go 1.25.0 + +require github.com/spf13/cobra v1.8.1 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/net v0.58.0 // indirect +) diff --git a/cli/go.sum b/cli/go.sum new file mode 100644 index 0000000..79e887c --- /dev/null +++ b/cli/go.sum @@ -0,0 +1,12 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cli/internal/cmd/root.go b/cli/internal/cmd/root.go new file mode 100644 index 0000000..20b12d6 --- /dev/null +++ b/cli/internal/cmd/root.go @@ -0,0 +1,46 @@ +// Package cmd wires up the signet CLI's command tree with Cobra. Subcommands +// that talk to a wallet, a keyring, or a Signet deployment live in their own +// internal packages (link, keys, spec) and are attached here as they land. +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func newRootCmd(version, commit string) *cobra.Command { + root := &cobra.Command{ + Use: "signet", + Short: "Link wallets and manage identity on the Signet registry", + Long: `signet is the command-line companion to the Signet developer identity +registry on Stellar/Soroban. + +It links a local wallet to an on-chain handle, manages signing keys, and +talks to a Signet deployment (the default hosted one, or a self-hosted +instance) over its HTTP API.`, + SilenceUsage: true, + SilenceErrors: true, + } + + // Cobra only renders the "Usage:" section of --help (and of a bare + // invocation) when the command is Runnable() or has subcommands — neither + // is true yet for a fresh scaffold with no subcommands attached. Giving it + // a RunE that just prints help keeps `signet` and `signet --help` both + // showing real usage instead of only the Long description. + root.RunE = func(cmd *cobra.Command, _ []string) error { + return cmd.Help() + } + + // Cobra wires --version to this automatically once Version is non-empty. + root.Version = fmt.Sprintf("%s (commit %s)", version, commit) + root.SetVersionTemplate("signet version {{.Version}}\n") + + return root +} + +// Execute builds the command tree and runs it against the process's +// arguments. main() only needs to report the error and set an exit code. +func Execute(version, commit string) error { + return newRootCmd(version, commit).Execute() +} diff --git a/cli/internal/cmd/root_test.go b/cli/internal/cmd/root_test.go new file mode 100644 index 0000000..1fba9b2 --- /dev/null +++ b/cli/internal/cmd/root_test.go @@ -0,0 +1,63 @@ +package cmd + +import ( + "bytes" + "strings" + "testing" +) + +func TestRootCmdVersion(t *testing.T) { + root := newRootCmd("1.2.3", "abc1234") + out := &bytes.Buffer{} + root.SetOut(out) + root.SetErr(out) + root.SetArgs([]string{"--version"}) + + if err := root.Execute(); err != nil { + t.Fatalf("--version returned an error: %v", err) + } + + got := out.String() + want := "signet version 1.2.3 (commit abc1234)\n" + if got != want { + t.Fatalf("--version output = %q, want %q", got, want) + } +} + +func TestRootCmdHelp(t *testing.T) { + root := newRootCmd("dev", "none") + out := &bytes.Buffer{} + root.SetOut(out) + root.SetErr(out) + root.SetArgs([]string{"--help"}) + + if err := root.Execute(); err != nil { + t.Fatalf("--help returned an error: %v", err) + } + + got := out.String() + if !strings.Contains(got, "signet") { + t.Fatalf("--help output does not mention the command name: %q", got) + } + if !strings.Contains(got, "Usage:") { + t.Fatalf("--help output does not print usage: %q", got) + } +} + +func TestRootCmdRunsWithoutArgs(t *testing.T) { + // No subcommand is implemented yet — the bare root command should still + // print its own help rather than erroring, so `signet` alone is a + // friendly landing page rather than a crash. + root := newRootCmd("dev", "none") + out := &bytes.Buffer{} + root.SetOut(out) + root.SetErr(out) + root.SetArgs([]string{}) + + if err := root.Execute(); err != nil { + t.Fatalf("running with no args returned an error: %v", err) + } + if !strings.Contains(out.String(), "Usage:") { + t.Fatalf("running with no args did not print help: %q", out.String()) + } +} diff --git a/cli/internal/keys/doc.go b/cli/internal/keys/doc.go new file mode 100644 index 0000000..8655892 --- /dev/null +++ b/cli/internal/keys/doc.go @@ -0,0 +1,4 @@ +// Package keys will manage local Stellar signing keys used by the CLI — +// generating, storing, and loading them for the link and spec commands to +// sign with. Scaffolded here; implemented in a follow-up issue. +package keys diff --git a/cli/internal/link/doc.go b/cli/internal/link/doc.go new file mode 100644 index 0000000..7af7836 --- /dev/null +++ b/cli/internal/link/doc.go @@ -0,0 +1,4 @@ +// Package link will bind a local wallet key to a Signet handle through the +// Identity Registry's claim/release/transfer operations. Scaffolded here; +// implemented in a follow-up issue. +package link diff --git a/cli/internal/spec/doc.go b/cli/internal/spec/doc.go new file mode 100644 index 0000000..a5f5574 --- /dev/null +++ b/cli/internal/spec/doc.go @@ -0,0 +1,5 @@ +// Package spec will hold the request/response types for a Signet +// deployment's HTTP API, so the rest of the CLI talks to it through typed +// Go values instead of hand-built JSON. Scaffolded here; implemented in a +// follow-up issue. +package spec From d886eefd0082ec698fc4a5134b89424e935a1e85 Mon Sep 17 00:00:00 2001 From: blockchain-maxis <267648998+blockchain-maxis@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:50:12 +0100 Subject: [PATCH 2/2] ci(cli): pin a golangci-lint built with Go 1.25 The v2.1.6 binary is built with go1.24 and refuses a module targeting go 1.25.0: can't load config: the Go language version (go1.24) used to build golangci-lint is lower than the targeted Go version (1.25.0) My earlier local check compiled it from source with go1.25.12, which is why it passed there and failed on CI. v2.13.2 ships built with go1.27; verified with the actual release binary against cli/: 0 issues. --- .github/workflows/ci.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 165cfae..c4833a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,14 +94,20 @@ jobs: - name: Test run: go test ./... - # golangci-lint-action v6 drives golangci-lint v1, whose flags differ from - # v2's — and cli/.golangci.yml is a v2 config (`version: "2"`, plus the - # v2-only `formatters:` block). Action v8 and a pinned v2 release keep the - # two in step; `version: latest` would also silently change what runs here - # from one week to the next. + # Three things have to agree here, and the original v6 + `version: latest` + # got two of them wrong: + # * cli/.golangci.yml is a v2 config (`version: "2"`, and the v2-only + # `formatters:` block), so the action must be v8 — v6 drives + # golangci-lint v1 and passes v1-only flags. + # * golangci-lint refuses to run when the Go version it was *built with* + # is older than the module's target, so this release has to be one + # built with >= go 1.25 to match cli/go.mod. v2.1.6 is built with + # go1.24 and fails with "can't load config: the Go language version + # (go1.24) ... is lower than the targeted Go version (1.25.0)". + # * `latest` would let both of those drift again without a commit here. - uses: golangci/golangci-lint-action@v8 with: - version: v2.1.6 + version: v2.13.2 working-directory: cli contracts: