Skip to content

Commit a3840c2

Browse files
committed
Wire in --app vs --name handling
1 parent e38aa07 commit a3840c2

7 files changed

Lines changed: 143 additions & 45 deletions

File tree

cmd/create.go

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
type createCmdFlags struct {
1616
app string
17+
name string
1718
EnvironmentVariables []string
1819
Secrets []string
1920
RevisionName string
@@ -27,6 +28,7 @@ type createReq struct {
2728

2829
type createResp struct {
2930
AppUrl string `json:"app_url"`
31+
ID string `json:"id"`
3032
}
3133

3234
func init() {
@@ -39,35 +41,42 @@ func init() {
3941
`),
4042
Example: heredoc.Doc(`
4143
$ gh runtime create --app my-app --env key1=value1 --env key2=value2 --secret key3=value3 --secret key4=value4
42-
# => Creates the app named 'my-app'
44+
# => Creates the app with the ID 'my-app'
45+
46+
$ gh runtime create --name my-new-app
47+
# => Creates a new app with the given name
4348
`),
4449
RunE: func(cmd *cobra.Command, args []string) error {
4550
client, err := api.DefaultRESTClient()
4651
if err != nil {
4752
return fmt.Errorf("failed creating REST client: %v", err)
4853
}
4954

50-
appUrl, err := runCreate(client, createCmdFlags)
55+
resp, err := runCreate(client, createCmdFlags)
5156
if err != nil {
5257
return err
5358
}
5459

55-
fmt.Printf("App created: %s\n", appUrl)
60+
fmt.Printf("App created: %s\n", resp.AppUrl)
61+
if resp.ID != "" {
62+
fmt.Printf("ID: %s\n", resp.ID)
63+
}
5664
return nil
5765
},
5866
}
5967

60-
createCmd.Flags().StringVarP(&createCmdFlags.app, "app", "a", "", "The app to create")
68+
createCmd.Flags().StringVarP(&createCmdFlags.app, "app", "a", "", "The app ID to create")
69+
createCmd.Flags().StringVarP(&createCmdFlags.name, "name", "n", "", "The name for the new app")
6170
createCmd.Flags().StringSliceVarP(&createCmdFlags.EnvironmentVariables, "env", "e", []string{}, "Environment variables to set on the app in the form 'key=value'")
6271
createCmd.Flags().StringSliceVarP(&createCmdFlags.Secrets, "secret", "s", []string{}, "Secrets to set on the app in the form 'key=value'")
6372
createCmd.Flags().StringVarP(&createCmdFlags.RevisionName, "revision-name", "r", "", "The revision name to use for the app")
6473
createCmd.Flags().BoolVar(&createCmdFlags.Init, "init", false, "Initialize a runtime.config.json file in the current directory after creating the app")
6574
rootCmd.AddCommand(createCmd)
6675
}
6776

68-
func runCreate(client restClient, flags createCmdFlags) (string, error) {
69-
if flags.app == "" {
70-
return "", fmt.Errorf("--app flag is required")
77+
func runCreate(client restClient, flags createCmdFlags) (createResp, error) {
78+
if flags.app == "" && flags.name == "" {
79+
return createResp{}, fmt.Errorf("either --app or --name flag is required")
7180
}
7281

7382
requestBody := createReq{
@@ -80,7 +89,7 @@ func runCreate(client restClient, flags createCmdFlags) (string, error) {
8089
if len(parts) == 2 {
8190
requestBody.EnvironmentVariables[parts[0]] = parts[1]
8291
} else {
83-
return "", fmt.Errorf("invalid environment variable format (%s). Must be in the form 'key=value'", pair)
92+
return createResp{}, fmt.Errorf("invalid environment variable format (%s). Must be in the form 'key=value'", pair)
8493
}
8594
}
8695

@@ -89,16 +98,22 @@ func runCreate(client restClient, flags createCmdFlags) (string, error) {
8998
if len(parts) == 2 {
9099
requestBody.Secrets[parts[0]] = parts[1]
91100
} else {
92-
return "", fmt.Errorf("invalid secret format (%s). Must be in the form 'key=value'", pair)
101+
return createResp{}, fmt.Errorf("invalid secret format (%s). Must be in the form 'key=value'", pair)
93102
}
94103
}
95104

96105
body, err := json.Marshal(requestBody)
97106
if err != nil {
98-
return "", fmt.Errorf("error marshalling request body: %v", err)
107+
return createResp{}, fmt.Errorf("error marshalling request body: %v", err)
108+
}
109+
110+
var createUrl string
111+
if flags.name != "" {
112+
createUrl = "runtime"
113+
} else {
114+
createUrl = fmt.Sprintf("runtime/%s/deployment", flags.app)
99115
}
100116

101-
createUrl := fmt.Sprintf("runtime/%s/deployment", flags.app)
102117
params := url.Values{}
103118
if flags.RevisionName != "" {
104119
params.Add("revision_name", flags.RevisionName)
@@ -110,15 +125,18 @@ func runCreate(client restClient, flags createCmdFlags) (string, error) {
110125
response := createResp{}
111126
err = client.Put(createUrl, bytes.NewReader(body), &response)
112127
if err != nil {
113-
return "", fmt.Errorf("error creating app: %v", err)
128+
return createResp{}, fmt.Errorf("error creating app: %v", err)
114129
}
115130

116131
if flags.Init {
117-
err = writeRuntimeConfig(flags.app, "")
132+
if response.ID == "" {
133+
return response, fmt.Errorf("error initializing config: server did not return an app ID")
134+
}
135+
err = writeRuntimeConfig(response.ID, "")
118136
if err != nil {
119-
return response.AppUrl, fmt.Errorf("error initializing config: %v", err)
137+
return response, fmt.Errorf("error initializing config: %v", err)
120138
}
121139
}
122140

123-
return response.AppUrl, nil
141+
return response, nil
124142
}

cmd/create_test.go

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,20 @@ import (
1010
"github.com/stretchr/testify/require"
1111
)
1212

13-
func TestRunCreate_NoApp(t *testing.T) {
13+
func buildCreateResponse(r createResp, resp interface{}) {
14+
if r.AppUrl == "" {
15+
r.AppUrl = "https://test-app.example.com"
16+
}
17+
if r.ID == "" {
18+
r.ID = "test-app-id"
19+
}
20+
*resp.(*createResp) = r
21+
}
22+
23+
func TestRunCreate_NoAppOrName(t *testing.T) {
1424
client := &mockRESTClient{}
1525
_, err := runCreate(client, createCmdFlags{})
16-
require.ErrorContains(t, err, "--app flag is required")
26+
require.ErrorContains(t, err, "either --app or --name flag is required")
1727
}
1828

1929
func TestRunCreate_InvalidEnvVarFormat(t *testing.T) {
@@ -43,17 +53,18 @@ func TestRunCreate_Success(t *testing.T) {
4353
putFunc: func(path string, body io.Reader, resp interface{}) error {
4454
capturedPath = path
4555
capturedBody, _ = io.ReadAll(body)
46-
return json.Unmarshal([]byte(`{"app_url":"https://new-app.example.com"}`), resp)
56+
buildCreateResponse(createResp{AppUrl: "https://my-app.example.com"}, resp)
57+
return nil
4758
},
4859
}
4960

50-
appUrl, err := runCreate(client, createCmdFlags{
61+
resp, err := runCreate(client, createCmdFlags{
5162
app: "my-app",
5263
EnvironmentVariables: []string{"KEY1=val1", "KEY2=val2"},
5364
Secrets: []string{"SECRET=sval"},
5465
})
5566
require.NoError(t, err)
56-
assert.Equal(t, "https://new-app.example.com", appUrl)
67+
assert.Equal(t, "https://my-app.example.com", resp.AppUrl)
5768
assert.Equal(t, "runtime/my-app/deployment", capturedPath)
5869

5970
var req createReq
@@ -65,9 +76,10 @@ func TestRunCreate_Success(t *testing.T) {
6576
func TestRunCreate_WithRevisionName(t *testing.T) {
6677
var capturedPath string
6778
client := &mockRESTClient{
68-
putFunc: func(path string, body io.Reader, resp interface{}) error {
79+
putFunc: func(path string, _ io.Reader, resp interface{}) error {
6980
capturedPath = path
70-
return json.Unmarshal([]byte(`{"app_url":"https://app.example.com"}`), resp)
81+
buildCreateResponse(createResp{AppUrl: "https://my-app.example.com"}, resp)
82+
return nil
7183
},
7284
}
7385

@@ -83,24 +95,28 @@ func TestRunCreate_WithInit(t *testing.T) {
8395
defer os.Chdir(origDir)
8496

8597
client := &mockRESTClient{
86-
putFunc: mockPutResponse(`{"app_url":"https://init-app.example.com"}`),
98+
putFunc: func(_ string, _ io.Reader, resp interface{}) error {
99+
buildCreateResponse(createResp{AppUrl: "https://init-app.example.com", ID: "init-app-id"}, resp)
100+
return nil
101+
},
87102
}
88103

89-
appUrl, err := runCreate(client, createCmdFlags{app: "init-app", Init: true})
104+
resp, err := runCreate(client, createCmdFlags{app: "init-app", Init: true})
90105
require.NoError(t, err)
91-
assert.Equal(t, "https://init-app.example.com", appUrl)
106+
assert.Equal(t, "https://init-app.example.com", resp.AppUrl)
92107

93108
data, err := os.ReadFile("runtime.config.json")
94109
require.NoError(t, err, "expected runtime.config.json to be created")
95-
assert.Contains(t, string(data), "init-app")
110+
assert.Contains(t, string(data), "init-app-id")
96111
}
97112

98113
func TestRunCreate_EnvVarWithEqualsInValue(t *testing.T) {
99114
var capturedBody []byte
100115
client := &mockRESTClient{
101-
putFunc: func(path string, body io.Reader, resp interface{}) error {
116+
putFunc: func(_ string, body io.Reader, resp interface{}) error {
102117
capturedBody, _ = io.ReadAll(body)
103-
return json.Unmarshal([]byte(`{"app_url":"https://app.example.com"}`), resp)
118+
buildCreateResponse(createResp{AppUrl: "https://my-app.example.com"}, resp)
119+
return nil
104120
},
105121
}
106122

@@ -111,3 +127,67 @@ func TestRunCreate_EnvVarWithEqualsInValue(t *testing.T) {
111127
json.Unmarshal(capturedBody, &req)
112128
assert.Equal(t, "val=with=equals", req.EnvironmentVariables["KEY"])
113129
}
130+
131+
func TestRunCreate_WithName(t *testing.T) {
132+
var capturedPath string
133+
client := &mockRESTClient{
134+
putFunc: func(path string, _ io.Reader, resp interface{}) error {
135+
capturedPath = path
136+
buildCreateResponse(createResp{AppUrl: "https://my-new-app.example.com", ID: "abc-123"}, resp)
137+
return nil
138+
},
139+
}
140+
141+
resp, err := runCreate(client, createCmdFlags{name: "my-new-app"})
142+
require.NoError(t, err)
143+
assert.Equal(t, "https://my-new-app.example.com", resp.AppUrl)
144+
assert.Equal(t, "abc-123", resp.ID)
145+
assert.Equal(t, "runtime", capturedPath)
146+
}
147+
148+
func TestRunCreate_WithNameAndInit(t *testing.T) {
149+
tmp := t.TempDir()
150+
origDir, _ := os.Getwd()
151+
os.Chdir(tmp)
152+
defer os.Chdir(origDir)
153+
154+
client := &mockRESTClient{
155+
putFunc: func(_ string, _ io.Reader, resp interface{}) error {
156+
buildCreateResponse(createResp{AppUrl: "https://named-app.example.com", ID: "def-456"}, resp)
157+
return nil
158+
},
159+
}
160+
161+
_, err := runCreate(client, createCmdFlags{name: "named-app", Init: true})
162+
require.NoError(t, err)
163+
164+
data, err := os.ReadFile("runtime.config.json")
165+
require.NoError(t, err)
166+
assert.Contains(t, string(data), "def-456")
167+
}
168+
169+
func TestRunCreate_ResponseWithID(t *testing.T) {
170+
client := &mockRESTClient{
171+
putFunc: func(_ string, _ io.Reader, resp interface{}) error {
172+
buildCreateResponse(createResp{AppUrl: "https://my-app.example.com", ID: "xyz-789"}, resp)
173+
return nil
174+
},
175+
}
176+
177+
resp, err := runCreate(client, createCmdFlags{app: "my-app"})
178+
require.NoError(t, err)
179+
assert.Equal(t, "https://my-app.example.com", resp.AppUrl)
180+
assert.Equal(t, "xyz-789", resp.ID)
181+
}
182+
183+
func TestRunCreate_InitWithoutIDInResponse(t *testing.T) {
184+
client := &mockRESTClient{
185+
putFunc: func(_ string, _ io.Reader, resp interface{}) error {
186+
b, _ := json.Marshal(createResp{AppUrl: "https://my-app.example.com"})
187+
return json.Unmarshal(b, resp)
188+
},
189+
}
190+
191+
_, err := runCreate(client, createCmdFlags{app: "my-app", Init: true})
192+
require.ErrorContains(t, err, "server did not return an app ID")
193+
}

cmd/delete.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func init() {
2424
`),
2525
Example: heredoc.Doc(`
2626
$ gh runtime delete --app my-app
27-
# => Deletes the app named 'my-app'
27+
# => Deletes the app with ID 'my-app'
2828
`),
2929
RunE: func(cmd *cobra.Command, args []string) error {
3030
client, err := api.DefaultRESTClient()
@@ -42,7 +42,7 @@ func init() {
4242
},
4343
}
4444

45-
deleteCmd.Flags().StringVarP(&deleteCmdFlags.app, "app", "a", "", "The app to delete")
45+
deleteCmd.Flags().StringVarP(&deleteCmdFlags.app, "app", "a", "", "The app ID to delete")
4646
deleteCmd.Flags().StringVarP(&deleteCmdFlags.revisionName, "revision-name", "r", "", "The revision name to use for the app")
4747
rootCmd.AddCommand(deleteCmd)
4848
}

cmd/deploy.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,18 @@ func init() {
3030
Short: "Deploy app to GitHub Runtime",
3131
Long: heredoc.Doc(`
3232
Deploys a directory to a GitHub Runtime app.
33-
You can specify the app name using --app flag, --config flag to read from a runtime config file,
33+
You can specify the app ID using --app flag, --config flag to read from a runtime config file,
3434
or it will automatically read from runtime.config.json in the current directory if it exists.
3535
`),
3636
Example: heredoc.Doc(`
3737
$ gh runtime deploy --dir ./dist --app my-app [--sha <sha>]
38-
# => Deploys the contents of the 'dist' directory to the app named 'my-app'.
38+
# => Deploys the contents of the 'dist' directory to the app with ID 'my-app'.
3939
4040
$ gh runtime deploy --dir ./dist --config runtime.config.json
41-
# => Deploys using app name from the config file.
41+
# => Deploys using app ID from the config file.
4242
4343
$ gh runtime deploy --dir ./dist
44-
# => Deploys using app name from runtime.config.json in current directory (if it exists).
44+
# => Deploys using app ID from runtime.config.json in current directory (if it exists).
4545
`),
4646
RunE: func(cmd *cobra.Command, args []string) error {
4747
client, err := api.DefaultRESTClient()
@@ -53,7 +53,7 @@ func init() {
5353
},
5454
}
5555
deployCmd.Flags().StringVarP(&deployCmdFlags.dir, "dir", "d", "", "The directory to deploy")
56-
deployCmd.Flags().StringVarP(&deployCmdFlags.app, "app", "a", "", "The app to deploy")
56+
deployCmd.Flags().StringVarP(&deployCmdFlags.app, "app", "a", "", "The app ID to deploy")
5757
deployCmd.Flags().StringVarP(&deployCmdFlags.config, "config", "c", "", "Path to runtime config file")
5858
deployCmd.Flags().StringVarP(&deployCmdFlags.revisionName, "revision-name", "r", "", "The revision name to deploy")
5959
deployCmd.Flags().StringVarP(&deployCmdFlags.sha, "sha", "s", "", "SHA of the app being deployed")

cmd/get.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,18 @@ func init() {
2727
Short: "Get details of a GitHub Runtime app",
2828
Long: heredoc.Doc(`
2929
Get details of a GitHub Runtime app.
30-
You can specify the app name using --app flag, --config flag to read from a runtime config file,
30+
You can specify the app ID using --app flag, --config flag to read from a runtime config file,
3131
or it will automatically read from runtime.config.json in the current directory if it exists.
3232
`),
3333
Example: heredoc.Doc(`
3434
$ gh runtime get --app my-app
35-
# => Retrieves details of the app named 'my-app'
35+
# => Retrieves details of the app with ID 'my-app'
3636
3737
$ gh runtime get --config runtime.config.json
38-
# => Retrieves details using app name from the config file.
38+
# => Retrieves details using app ID from the config file.
3939
4040
$ gh runtime get
41-
# => Retrieves details using app name from runtime.config.json in current directory (if it exists).
41+
# => Retrieves details using app ID from runtime.config.json in current directory (if it exists).
4242
`),
4343
RunE: func(cmd *cobra.Command, args []string) error {
4444
client, err := api.DefaultRESTClient()
@@ -56,7 +56,7 @@ func init() {
5656
},
5757
}
5858

59-
getCmd.Flags().StringVarP(&getCmdFlags.app, "app", "a", "", "The app to retrieve details for")
59+
getCmd.Flags().StringVarP(&getCmdFlags.app, "app", "a", "", "The app ID to retrieve details for")
6060
getCmd.Flags().StringVarP(&getCmdFlags.config, "config", "c", "", "Path to runtime config file")
6161
getCmd.Flags().StringVarP(&getCmdFlags.revisionName, "revision-name", "r", "", "The revision name to use for the app")
6262
rootCmd.AddCommand(getCmd)

cmd/init.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func init() {
2929
Long: heredoc.Doc(`
3030
Initialize a local project to connect it to a GitHub Spark app.
3131
This creates a runtime.config.json configuration file that binds your local project
32-
to a remote Spark app. You must specify an app name to validate the app exists.
32+
to a remote Spark app. You must specify an app ID to validate the app exists.
3333
Optionally specify an output path where the runtime.config.json file should be created.
3434
`),
3535
Example: heredoc.Doc(`
@@ -52,7 +52,7 @@ func init() {
5252
},
5353
}
5454

55-
initCmd.Flags().StringVarP(&initCmdFlags.app, "app", "a", "", "The app name to initialize")
55+
initCmd.Flags().StringVarP(&initCmdFlags.app, "app", "a", "", "The app ID to initialize")
5656
initCmd.Flags().StringVarP(&initCmdFlags.out, "out", "o", "", "The output path for the runtime.config.json file (default: runtime.config.json in current directory)")
5757
rootCmd.AddCommand(initCmd)
5858
}

0 commit comments

Comments
 (0)