Skip to content

Commit cea2bcd

Browse files
committed
updates
1 parent eabc9ba commit cea2bcd

File tree

11 files changed

+682
-186
lines changed

11 files changed

+682
-186
lines changed

cmd/app/delete.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func RunDeleteCommand(ctx context.Context, clients *shared.ClientFactory, cmd *c
111111
func confirmDeletion(ctx context.Context, IO iostreams.IOStreamer, app prompts.SelectedApp) (bool, error) {
112112
IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{
113113
Emoji: "warning",
114-
Text: style.Bold(" Danger zone"),
114+
Text: style.Bold("Danger zone"),
115115
Secondary: []string{
116116
fmt.Sprintf("App (%s) will be permanently deleted", app.App.AppID),
117117
"All triggers, workflows, and functions will be deleted",

cmd/sandbox/create.go

Lines changed: 62 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,12 @@
1515
package sandbox
1616

1717
import (
18-
"encoding/json"
1918
"fmt"
2019
"strconv"
2120
"strings"
2221
"time"
2322

2423
"github.com/slackapi/slack-cli/internal/shared"
25-
"github.com/slackapi/slack-cli/internal/shared/types"
2624
"github.com/slackapi/slack-cli/internal/slackerror"
2725
"github.com/slackapi/slack-cli/internal/style"
2826
"github.com/spf13/cobra"
@@ -36,25 +34,21 @@ type createFlags struct {
3634
owningOrgID string
3735
template string
3836
eventCode string
39-
ttl string
40-
autoLogin bool
41-
output string
42-
token string
37+
archiveTTL string // TTL duration, e.g. 1d, 2h
38+
archiveDate string // explicit date yyyy-mm-dd
4339
}
4440

4541
var createCmdFlags createFlags
4642

4743
func NewCreateCommand(clients *shared.ClientFactory) *cobra.Command {
4844
cmd := &cobra.Command{
4945
Use: "create [flags]",
50-
Short: "Create a new sandbox",
51-
Long: `Create a new Slack developer sandbox.
52-
53-
Provisions a new sandbox. Domain is derived from org name if --domain is not provided.`,
46+
Short: "Create a developer sandbox",
47+
Long: `Create a new Slack developer sandbox`,
5448
Example: style.ExampleCommandsf([]style.ExampleCommand{
55-
{Command: "sandbox create --name test-box", Meaning: "Create a sandbox named test-box"},
56-
{Command: "sandbox create --name test-box --password mypass --owning-org-id E12345", Meaning: "Create a sandbox with login password and owning org"},
57-
{Command: "sandbox create --name test-box --domain test-box --ttl 24h --output json", Meaning: "Create an ephemeral sandbox for CI/CD with JSON output"},
49+
{Command: "sandbox create --name test-box --password mypass", Meaning: "Create a sandbox named test-box"},
50+
{Command: "sandbox create --name test-box --password mypass --domain test-box --archive-ttl 1d", Meaning: "Create a temporary sandbox that will be archived in 1 day"},
51+
{Command: "sandbox create --name test-box --password mypass --domain test-box --archive-date 2025-12-31", Meaning: "Create a sandbox that will be archived on a specific date"},
5852
}),
5953
Args: cobra.NoArgs,
6054
PreRunE: func(cmd *cobra.Command, args []string) error {
@@ -68,79 +62,80 @@ Provisions a new sandbox. Domain is derived from org name if --domain is not pro
6862
cmd.Flags().StringVar(&createCmdFlags.name, "name", "", "Organization name for the new sandbox")
6963
cmd.Flags().StringVar(&createCmdFlags.domain, "domain", "", "Team domain (e.g., pizzaknifefight). If not provided, derived from org name")
7064
cmd.Flags().StringVar(&createCmdFlags.password, "password", "", "Password used to log into the sandbox")
71-
cmd.Flags().StringVar(&createCmdFlags.owningOrgID, "owning-org-id", "", "Enterprise team ID that manages your developer account, if applicable")
65+
cmd.Flags().StringVar(&createCmdFlags.locale, "locale", "", "Locale (eg. en-us, languageCode-countryCode)")
7266
cmd.Flags().StringVar(&createCmdFlags.template, "template", "", "Template ID for pre-defined data to preload")
7367
cmd.Flags().StringVar(&createCmdFlags.eventCode, "event-code", "", "Event code for the sandbox")
74-
cmd.Flags().StringVar(&createCmdFlags.ttl, "ttl", "", "Time-to-live duration; sandbox will be archived after this period (e.g., 2h, 1d, 7d)")
75-
cmd.Flags().StringVar(&createCmdFlags.output, "output", "text", "Output format: json, text")
76-
cmd.Flags().StringVar(&createCmdFlags.token, "token", "", "Service account token for CI/CD authentication")
68+
cmd.Flags().StringVar(&createCmdFlags.archiveTTL, "archive-ttl", "", "Time-to-live duration; sandbox will be archived at end of day after this period (e.g., 2h, 1d, 7d)")
69+
cmd.Flags().StringVar(&createCmdFlags.archiveDate, "archive-date", "", "Explicit archive date in yyyy-mm-dd format. Cannot be used with --archive")
7770

78-
cmd.MarkFlagRequired("name")
79-
cmd.MarkFlagRequired("domain")
80-
cmd.MarkFlagRequired("password")
71+
// If one's developer account is managed by multiple Production Slack teams, one of those team IDs must be provided in the command
72+
cmd.Flags().StringVar(&createCmdFlags.owningOrgID, "owning-org-id", "", "Enterprise team ID that manages your developer account, if applicable")
73+
74+
if err := cmd.MarkFlagRequired("name"); err != nil {
75+
panic(err)
76+
}
77+
if err := cmd.MarkFlagRequired("password"); err != nil {
78+
panic(err)
79+
}
8180

8281
return cmd
8382
}
8483

8584
func runCreateCommand(cmd *cobra.Command, clients *shared.ClientFactory) error {
8685
ctx := cmd.Context()
8786

88-
token, err := getSandboxToken(ctx, clients, createCmdFlags.token)
87+
auth, err := getSandboxAuth(ctx, clients)
8988
if err != nil {
9089
return err
9190
}
9291

9392
domain := createCmdFlags.domain
9493
if domain == "" {
95-
domain = slugFromsandboxName(createCmdFlags.name)
94+
domain = domainFromName(createCmdFlags.name)
9695
}
9796

98-
archiveDate, err := ttlToArchiveDate(createCmdFlags.ttl)
99-
if err != nil {
100-
return err
97+
if createCmdFlags.archiveTTL != "" && createCmdFlags.archiveDate != "" {
98+
return slackerror.New(slackerror.ErrInvalidArguments).
99+
WithMessage("Cannot use both --archive-ttl and --archive-date").
100+
WithRemediation("Use only one: --archive-ttl for TTL (e.g., 3d) or --archive-date for a specific date (yyyy-mm-dd)")
101+
}
102+
103+
archiveEpochDatetime := int64(0)
104+
if createCmdFlags.archiveTTL != "" {
105+
archiveEpochDatetime, err = getEpochFromTTL(createCmdFlags.archiveTTL)
106+
if err != nil {
107+
return err
108+
}
109+
} else if createCmdFlags.archiveDate != "" {
110+
archiveEpochDatetime, err = getEpochFromDate(createCmdFlags.archiveDate)
111+
if err != nil {
112+
return err
113+
}
101114
}
102115

103-
result, err := clients.API().CreateSandbox(ctx, token,
116+
teamID, sandboxURL, err := clients.API().CreateSandbox(ctx, auth.Token,
104117
createCmdFlags.name,
105118
domain,
106119
createCmdFlags.password,
107120
createCmdFlags.locale,
108121
createCmdFlags.owningOrgID,
109122
createCmdFlags.template,
110123
createCmdFlags.eventCode,
111-
archiveDate,
124+
archiveEpochDatetime,
112125
)
113126
if err != nil {
114127
return err
115128
}
116129

117-
switch createCmdFlags.output {
118-
case "json":
119-
encoder := json.NewEncoder(clients.IO.WriteOut())
120-
encoder.SetIndent("", " ")
121-
if err := encoder.Encode(result); err != nil {
122-
return err
123-
}
124-
default:
125-
printCreateSuccess(cmd, clients, result)
126-
}
127-
128-
if createCmdFlags.autoLogin && result.URL != "" {
129-
clients.Browser().OpenURL(result.URL)
130-
}
130+
printCreateSuccess(cmd, clients, teamID, sandboxURL)
131131

132132
return nil
133133
}
134134

135-
const maxTTL = 180 * 24 * time.Hour // 6 months
136-
137-
// ttlToArchiveDate parses a TTL string (e.g., "24h", "1d", "7d") and returns the Unix epoch
138-
// when the sandbox will be archived. Returns 0 if ttl is empty (no archiving). Supports
139-
// Go duration format (h, m, s) and "Nd" for days. TTL cannot exceed 6 months.
140-
func ttlToArchiveDate(ttl string) (int64, error) {
141-
if ttl == "" {
142-
return 0, nil
143-
}
135+
// getEpochFromTTL parses a time-to-live string (e.g., "24h", "1d", "7d") and returns the Unix epoch
136+
// when the sandbox will be archived. Supports Go duration format (h, m, s) and "Nd" for days.
137+
// The value cannot exceed 6 months.
138+
func getEpochFromTTL(ttl string) (int64, error) {
144139
var d time.Duration
145140
if strings.HasSuffix(strings.ToLower(ttl), "d") {
146141
numStr := strings.TrimSuffix(strings.ToLower(ttl), "d")
@@ -160,16 +155,23 @@ func ttlToArchiveDate(ttl string) (int64, error) {
160155
WithRemediation("Use a duration like 2h, 1d, or 7d")
161156
}
162157
}
163-
if d > maxTTL {
158+
return time.Now().Add(d).Unix(), nil
159+
}
160+
161+
// getEpochFromDate parses a date in yyyy-mm-dd format and returns the Unix epoch at start of that day (UTC).
162+
func getEpochFromDate(dateStr string) (int64, error) {
163+
dateFormat := "2006-01-02"
164+
t, err := time.ParseInLocation(dateFormat, dateStr, time.UTC)
165+
if err != nil {
164166
return 0, slackerror.New(slackerror.ErrInvalidArguments).
165-
WithMessage("TTL cannot exceed 6 months").
166-
WithRemediation("Use a shorter duration (e.g., 2h, 1d, 7d)")
167+
WithMessage("Invalid archive date: %q", dateStr).
168+
WithRemediation("Use yyyy-mm-dd format (e.g., 2025-12-31)")
167169
}
168-
return time.Now().Add(d).Unix(), nil
170+
return t.Unix(), nil
169171
}
170172

171-
// slugFromsandboxName derives a domain-safe slug from org name (lowercase, alphanumeric + hyphens).
172-
func slugFromsandboxName(name string) string {
173+
// domainFromName derives domain-safe text from the name of the sandbox (lowercase, alphanumeric + hyphens).
174+
func domainFromName(name string) string {
173175
var b []byte
174176
for _, r := range name {
175177
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
@@ -195,15 +197,15 @@ func slugFromsandboxName(name string) string {
195197
return string(b)
196198
}
197199

198-
func printCreateSuccess(cmd *cobra.Command, clients *shared.ClientFactory, result types.CreateSandboxResult) {
200+
func printCreateSuccess(cmd *cobra.Command, clients *shared.ClientFactory, teamID, url string) {
199201
ctx := cmd.Context()
200202
clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{
201203
Emoji: "beach_with_umbrella",
202204
Text: " Sandbox Created",
203205
Secondary: []string{
204-
fmt.Sprintf("Team ID: %s", result.TeamID),
205-
fmt.Sprintf("User ID: %s", result.UserID),
206-
fmt.Sprintf("URL: %s", result.URL),
206+
fmt.Sprintf("Team ID: %s", teamID),
207+
fmt.Sprintf("URL: %s", url),
207208
},
208209
}))
210+
clients.IO.PrintInfo(ctx, false, "Manage this sandbox from the CLI or visit\n%s", style.Secondary("https://api.slack.com/developer-program/sandboxes"))
209211
}

0 commit comments

Comments
 (0)