# repo create: stamp a usable entire:// remote in the JSON output

`7de7dce`→[main](/content/gh/entireio/cli/commits/main/index.html)·

stiak·1mo ago·2 files·+210 added/-1 removed

`entire repo create` echoed the raw Repo wire JSON, which has clusterHost
and path but no ready-to-use clone/remote URL. Synthesize a `remote` field
of the form entire://<clusterHost>/<path> (the form git clone and
git-remote-entire accept), merged into the create output.

The path the API returns carries a leading slash, so trim it before
joining to avoid a doubled separator. Omit the field entirely when either
coordinate is unresolved (e.g. a still-provisioning repo) rather than
emit a half-formed URL. The synthesis only fills a gap: if the wire object
already carries a `remote` (a future first-class field, or one arriving via
additional properties) it's left untouched so the server value wins.

Repo carries a custom marshaler plus additional properties, so the repo is
round-tripped through its own encoder and the remote merged in rather than
embedded in a wrapper struct.

## Sessions

## Changes

2

- cmd/entire/cli
  
  - Mrepo.go+54/-1
  
  - Arepo_test.go+156

```go
import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

"github.com/spf13/cobra"
)

// repoRemoteURL synthesizes the entire:// clone/remote URL for a repo from
// its resolved cluster host and path — the form `git clone` and
// `git remote add` accept, which git-remote-entire reads back as the repo
// slug from the URL path. Returns "" when either coordinate is missing (a
// still-provisioning repo may not have them yet); a half-formed URL is worse
// than none.
func repoRemoteURL(r coreapi.Repo) string {
	host := strings.TrimSpace(r.ClusterHost.Or(""))
	path := strings.TrimSpace(r.Path.Or(""))
	if host == "" || path == "" {
		return ""
	}
	return "entire://" + host + "/" + strings.TrimPrefix(path, "/")
}

// repoCreateOutput renders a created repo as JSON with a synthesized `remote`
// field merged in — the entire:// URL callers paste into `git clone` or
// `git remote add`. The repo carries a custom marshaler plus arbitrary
// additional properties, so it can't simply be embedded in a wrapper struct;
// instead it's round-tripped through its own encoder and the remote is merged
// into the resulting object. The synthesis only fills a gap: if the wire
// object already carries a `remote` (a future first-class field, or one
// arriving via additional properties) it's left untouched, so the
// server-provided value always wins. The field is omitted when the clone
// coordinates aren't resolvable yet rather than emitted half-formed.
func repoCreateOutput(r *coreapi.Repo) (any, error) {
	raw, err := json.Marshal(r)
	if err != nil {
		return nil, fmt.Errorf("encode repo: %w", err)
	}
	var obj map[string]json.RawMessage
	if err := json.Unmarshal(raw, &obj); err != nil {
		return nil, fmt.Errorf("decode repo: %w", err)
	}
	if _, ok := obj["remote"]; !ok {
		if remote := repoRemoteURL(*r); remote != "" {
			encoded, err := json.Marshal(remote)
			if err != nil {
				return nil, fmt.Errorf("encode remote: %w", err)
			}
			obj["remote"] = encoded
		}
	}
	return obj, nil
}

func TestRepoRemoteURL(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name string
		repo coreapi.Repo
		want string
	}{
		{
			name: "host and path produce an entire:// URL",
			repo: coreapi.Repo{
				ClusterHost: coreapi.NewOptString("aws-us-east-2.entire.io"),
				Path:        coreapi.NewOptString("acme/web"),
			},
			want: "entire://aws-us-east-2.entire.io/acme/web",
		},
		{
			name: "leading slash on path is not doubled",
			repo: coreapi.Repo{
				ClusterHost: coreapi.NewOptString("aws-us-east-2.entire.io"),
				Path:        coreapi.NewOptString("/acme/web"),
			},
			want: "entire://aws-us-east-2.entire.io/acme/web",
		},
		{
			name: "missing host yields no URL",
			repo: coreapi.Repo{Path: coreapi.NewOptString("acme/web")},
			want: "",
		},
		{
			name: "missing path yields no URL",
			repo: coreapi.Repo{ClusterHost: coreapi.NewOptString("aws-us-east-2.entire.io")},
			want: "",
		},
		{
			name: "blank coordinates yield no URL",
			repo: coreapi.Repo{
				ClusterHost: coreapi.NewOptString("  "),
				Path:        coreapi.NewOptString(""),
			},
			want: "",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			if got := repoRemoteURL(tt.repo); got != tt.want {
				t.Errorf("repoRemoteURL() = %q, want %q", got, tt.want)
			}
		})
	}
}
```
