Make control-plane create verbs human-by-default · Entire
Make control-plane create verbs human-by-default
ab6c6e5→main
peyton-alt·2w ago·8 files·+130 added/-33 removed
org/project/repo create and grant add were the only CLI verbs whose default output was raw JSON. They now print '✓ Created …' confirmations like the rest of the CLI; the full wire JSON stays available via --json.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Sessions
990b88bcc14aView transcript
Changes
8
cmd/entire/cli
Mcorecmd.go+15/-9
Mcorecmd_list_test.go+9/-1
Acorecmd_mutation_test.go+51
Mgrant.go+25/-13
Morg.go+7/-2
Mproject.go+7/-3
Mrepo.go+12/-4
Mrepo_mirror.go+4/-1
353 unmodified lines
// runCoreJSON runs fn against an authenticated control-plane client and // prints its result as indented JSON. It owns the preamble every // control-plane command shares: silence usage so input errors don't spam // the usage block, build the client, and map an API error to a // problem-detail SilentError. Commands supply only the call + the value to // render. func runCoreJSON(cmd *cobra.Command, fn func(ctx context.Context, c *coreapi.Client) (any, error)) error { // runCoreMutation runs fn against the control plane and renders its outcome // the way the rest of the CLI renders mutations: the ✓ human confirmation on // stdout by default, or the wire object as JSON when --json was passed. fn // returns both so the human line can name the created resource while --json // preserves the full wire model (additive-only: synthesized fields like the // repo remote URL are merged in, nothing is ever omitted). It owns the same // preamble as the other runCore variants: silence usage, build the client, // map API errors to problem-detail messages. func runCoreMutation(cmd *cobra.Command, fn func(ctx context.Context, c *coreapi.Client) (message string, wire any, err error)) error { return runCore(cmd, func(ctx context.Context, c *coreapi.Client) error { out, err := fn(ctx, c) message, wire, err := fn(ctx, c) if err != nil { return err } return printJSON(cmd.OutOrStdout(), out) if jsonRequested(cmd) { return printJSON(cmd.OutOrStdout(), wire) } fmt.Fprintln(cmd.OutOrStdout(), message) return nil }) }
Mcmd/entire/cli/corecmd.go+15/-9
10 unmodified lines
// listCmd builds a minimal command wired to renderCoreList via runCoreList so
// runListRender builds a minimal command wired to renderCoreList via runCoreList so
// the rendering path (table / empty state / --json) is exercised without a
// server: fn returns items directly.
func runListRender(t *testing.T, jsonFlag bool, items []coreapi.Org) (stdout, stderr string) {
// 38 unmodified lines
out, _ := runListRender(t, true, nil)
require.JSONEq(t, "[]", out, "empty --json list must be [], not null")
}
// Not parallel: swaps the package-level activeCoreClient seam.
func TestRunCoreList_RendersRows(t *testing.T) {
out, errOut := runListRender(t, false, []coreapi.Org{{ID: testDeleteULID, Name: "acme", Region: "us"}})
require.Contains(t, out, "NAME")
require.Contains(t, out, "acme")
require.Empty(t, errOut)
}
Mcmd/entire/cli/corecmd_list_test.go+9/-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package cli
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/entireio/cli/internal/coreapi"
)
// newCreateOrgServer answers POST /api/v1/orgs with a created org. The 201
// status is load-bearing: the generated decodeCreateOrgResponse only accepts
// http.StatusCreated — a default 200 makes CreateOrg return an error.
func newCreateOrgServer(t *testing.T) *httptest.Server {
t.Helper()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
if err := writeJSON(w, &coreapi.Org{ID: testDeleteULID, Name: "acme", Region: "us"}); err != nil {
t.Errorf("encode org: %v", err)
}
}))
t.Cleanup(srv.Close)
return srv
}
// Not parallel: runDeleteCmd swaps the package-level activeCoreClient seam.
func TestOrgCreate_HumanByDefault(t *testing.T) {
srv := newCreateOrgServer(t)
out, errOut, err := runDeleteCmd(t, newOrgCreateCmd, srv.URL, "acme")
require.NoError(t, err)
require.Contains(t, out, "✓ Created org acme ("+testDeleteULID+")")
require.NotContains(t, out, "{" , "default output must not be JSON")
require.Empty(t, errOut)
}
// Not parallel: runDeleteCmd swaps the package-level activeCoreClient seam.
func TestOrgCreate_JSONOnRequest(t *testing.T) {
srv := newCreateOrgServer(t)
// org create's --json is persistent on the group root, so drive the
// full group command with "create" as a subcommand arg.
out, _, err := runDeleteCmd(t, newOrgCmd, srv.URL, "create", "acme", "--json")
require.NoError(t, err)
require.Contains(t, out, `"name": "acme"`)
require.Contains(t, out, `"id": "+testDeleteULID+"`)
require.NotContains(t, out, "✓ Created")
}
Acmd/entire/cli/corecmd_mutation_test.go+51/-13
116 unmodified lines
Example: " entire grant org add acme github:alice --role admin", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { return runCoreJSON(cmd, func(ctx context.Context, c *coreapi.Client) (any, error) { return runCoreMutation(cmd, func(ctx context.Context, c *coreapi.Client) (string, any, error) { orgID, err := resolveOrgRef(ctx, c, args[0]) if err != nil { return nil, err } provider, providerUserID, err := resolveGranteeProvider(ctx, c, args[1]) if err != nil { return nil, err } body := &coreapi.AddOrgMemberInputBody{ Provider: provider, }
}
Mcmd/entire/cli/grant.go+25/-13
1 unmodified line
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/entireio/cli/internal/coreapi"
)
// Short: "Create an organization",
// Args: cobra.ExactArgs(1),
// RunE: func(cmd *cobra.Command, args []string) error {
// return runCoreJSON(cmd, func(ctx context.Context, c *coreapi.Client) (any, error) {
// return runCoreMutation(cmd, func(ctx context.Context, c *coreapi.Client) (string, any, error) {
// }
Mcmd/entire/cli/org.go+7/-2
54 unmodified lines
if err != nil { return err } return runCoreJSON(cmd, func(ctx context.Context, c *coreapi.Client) (any, error) { return runCoreMutation(cmd, func(ctx context.Context, c *coreapi.Client) (string, any, error) { // Orgs are addressed by name, accounts by github:handle; both // also accept a raw ULID. var ownerRef string
Mcmd/entire/cli/project.go+7/-3
120 unmodified lines
// Short: "Create a repository in a project",
// Args: cobra.ExactArgs(1),
// RunE: func(cmd *cobra.Command, args []string) error {
// return runCoreJSON(cmd, func(ctx context.Context, c *coreapi.Client) (any, error) {
// return runCoreMutation(cmd, func(ctx context.Context, c *coreapi.Client) (string, any, error) {
// }
Mcmd/entire/cli/repo.go+12/-4
290 unmodified lines