Skip to content

Commit f200970

Browse files
committed
updates
1 parent eabc9ba commit f200970

File tree

11 files changed

+698
-172
lines changed

11 files changed

+698
-172
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: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"time"
2323

2424
"github.com/slackapi/slack-cli/internal/shared"
25-
"github.com/slackapi/slack-cli/internal/shared/types"
2625
"github.com/slackapi/slack-cli/internal/slackerror"
2726
"github.com/slackapi/slack-cli/internal/style"
2827
"github.com/spf13/cobra"
@@ -35,26 +34,24 @@ type createFlags struct {
3534
locale string
3635
owningOrgID string
3736
template string
37+
demoIDs []string
3838
eventCode string
39-
ttl string
40-
autoLogin bool
39+
archiveTTL string // TTL duration, e.g. 1d, 2h
40+
archiveDate string // explicit date yyyy-mm-dd
4141
output string
42-
token string
4342
}
4443

4544
var createCmdFlags createFlags
4645

4746
func NewCreateCommand(clients *shared.ClientFactory) *cobra.Command {
4847
cmd := &cobra.Command{
4948
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.`,
49+
Short: "Create a developer sandbox",
50+
Long: `Create a new Slack developer sandbox`,
5451
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"},
52+
{Command: "sandbox create --name test-box --password mypass", Meaning: "Create a sandbox named test-box"},
53+
{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"},
54+
{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"},
5855
}),
5956
Args: cobra.NoArgs,
6057
PreRunE: func(cmd *cobra.Command, args []string) error {
@@ -68,24 +65,34 @@ Provisions a new sandbox. Domain is derived from org name if --domain is not pro
6865
cmd.Flags().StringVar(&createCmdFlags.name, "name", "", "Organization name for the new sandbox")
6966
cmd.Flags().StringVar(&createCmdFlags.domain, "domain", "", "Team domain (e.g., pizzaknifefight). If not provided, derived from org name")
7067
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")
68+
cmd.Flags().StringVar(&createCmdFlags.locale, "locale", "", "Locale (IANA timezone, e.g. America/New_York) for sandbox and for interpreting archive dates")
7269
cmd.Flags().StringVar(&createCmdFlags.template, "template", "", "Template ID for pre-defined data to preload")
70+
cmd.Flags().StringSliceVar(&createCmdFlags.demoIDs, "demo-ids", nil, "Demo IDs to preload in the sandbox")
7371
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)")
72+
cmd.Flags().StringVar(&createCmdFlags.archiveTTL, "archive-ttl", "", "Time-to-live duration; sandbox will be archived after this period (e.g., 2h, 1d, 7d)")
73+
cmd.Flags().StringVar(&createCmdFlags.archiveDate, "archive-date", "", "Explicit archive date in yyyy-mm-dd format. Cannot be used with --archive")
7574
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")
7775

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

8289
return cmd
8390
}
8491

8592
func runCreateCommand(cmd *cobra.Command, clients *shared.ClientFactory) error {
8693
ctx := cmd.Context()
8794

88-
token, err := getSandboxToken(ctx, clients, createCmdFlags.token)
95+
auth, err := getSandboxAuth(ctx, clients)
8996
if err != nil {
9097
return err
9198
}
@@ -95,20 +102,34 @@ func runCreateCommand(cmd *cobra.Command, clients *shared.ClientFactory) error {
95102
domain = slugFromsandboxName(createCmdFlags.name)
96103
}
97104

98-
archiveDate, err := ttlToArchiveDate(createCmdFlags.ttl)
99-
if err != nil {
100-
return err
105+
if createCmdFlags.archiveTTL != "" && createCmdFlags.archiveDate != "" {
106+
return slackerror.New(slackerror.ErrInvalidArguments).
107+
WithMessage("Cannot use both --archive-ttl and --archive-date").
108+
WithRemediation("Use only one: --archive-ttl for TTL (e.g., 3d) or --archive-date for a specific date (yyyy-mm-dd)")
101109
}
102110

103-
result, err := clients.API().CreateSandbox(ctx, token,
111+
archiveEpochDatetime := int64(0)
112+
if createCmdFlags.archiveTTL != "" {
113+
archiveEpochDatetime, err = getEpochFromTTL(createCmdFlags.archiveTTL)
114+
if err != nil {
115+
return err
116+
}
117+
} else if createCmdFlags.archiveDate != "" {
118+
archiveEpochDatetime, err = getEpochFromDate(createCmdFlags.archiveDate, createCmdFlags.locale)
119+
if err != nil {
120+
return err
121+
}
122+
}
123+
124+
teamID, sandboxURL, err := clients.API().CreateSandbox(ctx, auth.Token,
104125
createCmdFlags.name,
105126
domain,
106127
createCmdFlags.password,
107128
createCmdFlags.locale,
108129
createCmdFlags.owningOrgID,
109130
createCmdFlags.template,
110131
createCmdFlags.eventCode,
111-
archiveDate,
132+
archiveEpochDatetime,
112133
)
113134
if err != nil {
114135
return err
@@ -118,29 +139,20 @@ func runCreateCommand(cmd *cobra.Command, clients *shared.ClientFactory) error {
118139
case "json":
119140
encoder := json.NewEncoder(clients.IO.WriteOut())
120141
encoder.SetIndent("", " ")
121-
if err := encoder.Encode(result); err != nil {
142+
if err := encoder.Encode(map[string]string{"team_id": teamID, "url": sandboxURL}); err != nil {
122143
return err
123144
}
124145
default:
125-
printCreateSuccess(cmd, clients, result)
126-
}
127-
128-
if createCmdFlags.autoLogin && result.URL != "" {
129-
clients.Browser().OpenURL(result.URL)
146+
printCreateSuccess(cmd, clients, teamID, sandboxURL)
130147
}
131148

132149
return nil
133150
}
134151

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-
}
152+
// getEpochFromTTL parses a time-to-live string (e.g., "24h", "1d", "7d") and returns the Unix epoch
153+
// when the sandbox will be archived. Supports Go duration format (h, m, s) and "Nd" for days.
154+
// The value cannot exceed 6 months.
155+
func getEpochFromTTL(ttl string) (int64, error) {
144156
var d time.Duration
145157
if strings.HasSuffix(strings.ToLower(ttl), "d") {
146158
numStr := strings.TrimSuffix(strings.ToLower(ttl), "d")
@@ -160,12 +172,30 @@ func ttlToArchiveDate(ttl string) (int64, error) {
160172
WithRemediation("Use a duration like 2h, 1d, or 7d")
161173
}
162174
}
163-
if d > maxTTL {
175+
return time.Now().Add(d).Unix(), nil
176+
}
177+
178+
// getEpochFromDate parses a date in yyyy-mm-dd format and returns the Unix epoch at start of that day
179+
// in the given locale (IANA timezone). If locale is empty, UTC is used.
180+
func getEpochFromDate(dateStr string, locale string) (int64, error) {
181+
loc := time.UTC
182+
if locale != "" {
183+
var err error
184+
loc, err = time.LoadLocation(locale)
185+
if err != nil {
186+
return 0, slackerror.New(slackerror.ErrInvalidArguments).
187+
WithMessage("Invalid locale: %q", locale).
188+
WithRemediation("Use an IANA timezone (e.g., America/New_York, Europe/London)")
189+
}
190+
}
191+
dateFormat := "2006-01-02"
192+
t, err := time.ParseInLocation(dateFormat, dateStr, loc)
193+
if err != nil {
164194
return 0, slackerror.New(slackerror.ErrInvalidArguments).
165-
WithMessage("TTL cannot exceed 6 months").
166-
WithRemediation("Use a shorter duration (e.g., 2h, 1d, 7d)")
195+
WithMessage("Invalid archive date: %q", dateStr).
196+
WithRemediation("Use yyyy-mm-dd format (e.g., 2025-12-31)")
167197
}
168-
return time.Now().Add(d).Unix(), nil
198+
return t.Unix(), nil
169199
}
170200

171201
// slugFromsandboxName derives a domain-safe slug from org name (lowercase, alphanumeric + hyphens).
@@ -195,15 +225,14 @@ func slugFromsandboxName(name string) string {
195225
return string(b)
196226
}
197227

198-
func printCreateSuccess(cmd *cobra.Command, clients *shared.ClientFactory, result types.CreateSandboxResult) {
228+
func printCreateSuccess(cmd *cobra.Command, clients *shared.ClientFactory, teamID, url string) {
199229
ctx := cmd.Context()
200230
clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{
201231
Emoji: "beach_with_umbrella",
202232
Text: " Sandbox Created",
203233
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),
234+
fmt.Sprintf("Team ID: %s", teamID),
235+
fmt.Sprintf("URL: %s", url),
207236
},
208237
}))
209238
}

0 commit comments

Comments
 (0)