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

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