Skip to content

Commit 98ecc15

Browse files
committed
updates
1 parent eabc9ba commit 98ecc15

File tree

11 files changed

+708
-170
lines changed

11 files changed

+708
-170
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: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -22,39 +22,36 @@ 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"
2928
)
3029

3130
type createFlags struct {
32-
name string
33-
domain string
34-
password string
35-
locale string
36-
owningOrgID string
37-
template string
38-
eventCode string
39-
ttl string
40-
autoLogin bool
41-
output string
42-
token string
31+
name string
32+
domain string
33+
password string
34+
locale string
35+
owningOrgID string
36+
template string
37+
demoIDs []string
38+
eventCode string
39+
archiveDate string // TTL duration, e.g. 1d, 2h
40+
archiveDateStr string // explicit date yyyy-mm-dd
41+
output 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 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,33 @@ 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")
7268
cmd.Flags().StringVar(&createCmdFlags.template, "template", "", "Template ID for pre-defined data to preload")
69+
cmd.Flags().StringSliceVar(&createCmdFlags.demoIDs, "demo-ids", nil, "Demo IDs to preload in the sandbox")
7370
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)")
71+
cmd.Flags().StringVar(&createCmdFlags.archiveDate, "archive", "", "Time-to-live duration; sandbox will be archived after this period (e.g., 2h, 1d, 7d)")
72+
cmd.Flags().StringVar(&createCmdFlags.archiveDateStr, "archive-date", "", "Explicit archive date in yyyy-mm-dd format. Cannot be used with --archive")
7573
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")
7774

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

8288
return cmd
8389
}
8490

8591
func runCreateCommand(cmd *cobra.Command, clients *shared.ClientFactory) error {
8692
ctx := cmd.Context()
8793

88-
token, err := getSandboxToken(ctx, clients, createCmdFlags.token)
94+
auth, err := getSandboxAuth(ctx, clients)
8995
if err != nil {
9096
return err
9197
}
@@ -95,12 +101,18 @@ func runCreateCommand(cmd *cobra.Command, clients *shared.ClientFactory) error {
95101
domain = slugFromsandboxName(createCmdFlags.name)
96102
}
97103

98-
archiveDate, err := ttlToArchiveDate(createCmdFlags.ttl)
104+
if createCmdFlags.archiveDate != "" && createCmdFlags.archiveDateStr != "" {
105+
return slackerror.New(slackerror.ErrInvalidArguments).
106+
WithMessage("Cannot use both --archive and --archive-date").
107+
WithRemediation("Use only one: --archive for TTL (e.g., 1d) or --archive-date for a specific date (yyyy-mm-dd)")
108+
}
109+
110+
archiveDate, err := getArchiveDateEpoch(createCmdFlags.archiveDate, createCmdFlags.archiveDateStr)
99111
if err != nil {
100112
return err
101113
}
102114

103-
result, err := clients.API().CreateSandbox(ctx, token,
115+
teamID, sandboxURL, err := clients.API().CreateSandbox(ctx, auth.Token,
104116
createCmdFlags.name,
105117
domain,
106118
createCmdFlags.password,
@@ -118,29 +130,37 @@ func runCreateCommand(cmd *cobra.Command, clients *shared.ClientFactory) error {
118130
case "json":
119131
encoder := json.NewEncoder(clients.IO.WriteOut())
120132
encoder.SetIndent("", " ")
121-
if err := encoder.Encode(result); err != nil {
133+
if err := encoder.Encode(map[string]string{"team_id": teamID, "url": sandboxURL}); err != nil {
122134
return err
123135
}
124136
default:
125-
printCreateSuccess(cmd, clients, result)
126-
}
127-
128-
if createCmdFlags.autoLogin && result.URL != "" {
129-
clients.Browser().OpenURL(result.URL)
137+
printCreateSuccess(cmd, clients, teamID, sandboxURL)
130138
}
131139

132140
return nil
133141
}
134142

135143
const maxTTL = 180 * 24 * time.Hour // 6 months
136144

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
145+
const archiveDateFormat = "2006-01-02"
146+
147+
// getArchiveDateEpoch returns the Unix epoch when the sandbox will be archived.
148+
// It accepts either a TTL string (--archive) or an explicit date (--archive-date).
149+
// Returns 0 if neither is provided.
150+
func getArchiveDateEpoch(ttl, dateStr string) (int64, error) {
151+
if ttl != "" {
152+
return getArchiveDateFromTTL(ttl)
153+
}
154+
if dateStr != "" {
155+
return getArchiveDateFromDate(dateStr)
143156
}
157+
return 0, nil
158+
}
159+
160+
// getArchiveDateFromTTL parses a time-to-live string (e.g., "24h", "1d", "7d") and returns the Unix epoch
161+
// when the sandbox will be archived. Supports Go duration format (h, m, s) and "Nd" for days.
162+
// The value cannot exceed 6 months.
163+
func getArchiveDateFromTTL(ttl string) (int64, error) {
144164
var d time.Duration
145165
if strings.HasSuffix(strings.ToLower(ttl), "d") {
146166
numStr := strings.TrimSuffix(strings.ToLower(ttl), "d")
@@ -168,6 +188,17 @@ func ttlToArchiveDate(ttl string) (int64, error) {
168188
return time.Now().Add(d).Unix(), nil
169189
}
170190

191+
// getArchiveDateFromDate parses a date in yyyy-mm-dd format and returns the Unix epoch at start of that day (UTC).
192+
func getArchiveDateFromDate(dateStr string) (int64, error) {
193+
t, err := time.ParseInLocation(archiveDateFormat, dateStr, time.UTC)
194+
if err != nil {
195+
return 0, slackerror.New(slackerror.ErrInvalidArguments).
196+
WithMessage("Invalid archive date: %q", dateStr).
197+
WithRemediation("Use yyyy-mm-dd format (e.g., 2025-12-31)")
198+
}
199+
return t.Unix(), nil
200+
}
201+
171202
// slugFromsandboxName derives a domain-safe slug from org name (lowercase, alphanumeric + hyphens).
172203
func slugFromsandboxName(name string) string {
173204
var b []byte
@@ -195,15 +226,14 @@ func slugFromsandboxName(name string) string {
195226
return string(b)
196227
}
197228

198-
func printCreateSuccess(cmd *cobra.Command, clients *shared.ClientFactory, result types.CreateSandboxResult) {
229+
func printCreateSuccess(cmd *cobra.Command, clients *shared.ClientFactory, teamID, url string) {
199230
ctx := cmd.Context()
200231
clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{
201232
Emoji: "beach_with_umbrella",
202233
Text: " Sandbox Created",
203234
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),
235+
fmt.Sprintf("Team ID: %s", teamID),
236+
fmt.Sprintf("URL: %s", url),
207237
},
208238
}))
209239
}

0 commit comments

Comments
 (0)