@@ -7,17 +7,47 @@ import (
77 "io"
88 "net/http"
99 "os"
10+ "regexp"
1011 "text/tabwriter"
1112
1213 "github.com/instant-dev/cli/internal/tokens"
1314 "github.com/spf13/cobra"
1415)
1516
1617// ── Provisioning subcommand groups ───────────────────────────────────────────
17- // instant db new [name]
18- // instant cache new [name]
19- // instant nosql new [name]
20- // instant queue new [name]
18+ // instant db new --name <name>
19+ // instant cache new --name <name>
20+ // instant nosql new --name <name>
21+ // instant queue new --name <name>
22+ //
23+ // The resource `name` is REQUIRED on every provisioning endpoint. The server
24+ // enforces 1–64 chars matching nameRegexp and rejects an omitted name with
25+ // HTTP 400; the CLI marks --name required so the error surfaces locally
26+ // before any API round trip.
27+
28+ // nameMaxLen and nameRegexp mirror the server-side resource-name contract
29+ // (1–64 chars, must start with an alphanumeric character).
30+ const nameMaxLen = 64
31+
32+ var nameRegexp = regexp .MustCompile (`^[A-Za-z0-9][A-Za-z0-9 _-]*$` )
33+
34+ // resourceName is bound to the required --name flag on every `new` command.
35+ var resourceName string
36+
37+ // validateResourceName applies the server-side name contract locally so the
38+ // CLI fails fast with a clear message instead of a bare HTTP 400.
39+ func validateResourceName (name string ) error {
40+ if name == "" {
41+ return fmt .Errorf ("--name is required" )
42+ }
43+ if len (name ) > nameMaxLen {
44+ return fmt .Errorf ("--name must be 1–%d characters (got %d)" , nameMaxLen , len (name ))
45+ }
46+ if ! nameRegexp .MatchString (name ) {
47+ return fmt .Errorf ("--name %q is invalid: must match %s" , name , nameRegexp .String ())
48+ }
49+ return nil
50+ }
2151
2252var (
2353 dbCmd = & cobra.Command {Use : "db" , Short : "Manage Postgres database resources" }
@@ -27,40 +57,45 @@ var (
2757)
2858
2959var dbNewCmd = & cobra.Command {
30- Use : "new [name]" ,
31- Short : "Provision a Postgres database (+ pgvector)" ,
32- Args : cobra .MaximumNArgs (1 ),
33- RunE : makeProvisionCmd ("/db/new" , "db" ),
60+ Use : "new --name <name>" ,
61+ Short : "Provision a Postgres database (+ pgvector)" ,
62+ Example : " instant db new --name app-db" ,
63+ Args : cobra .NoArgs ,
64+ RunE : makeProvisionCmd ("/db/new" , "db" ),
3465}
3566
3667var cacheNewCmd = & cobra.Command {
37- Use : "new [name]" ,
38- Short : "Provision a Redis cache" ,
39- Args : cobra .MaximumNArgs (1 ),
40- RunE : makeProvisionCmd ("/cache/new" , "cache" ),
68+ Use : "new --name <name>" ,
69+ Short : "Provision a Redis cache" ,
70+ Example : " instant cache new --name app-cache" ,
71+ Args : cobra .NoArgs ,
72+ RunE : makeProvisionCmd ("/cache/new" , "cache" ),
4173}
4274
4375var nosqlNewCmd = & cobra.Command {
44- Use : "new [name]" ,
45- Short : "Provision a MongoDB document store" ,
46- Args : cobra .MaximumNArgs (1 ),
47- RunE : makeProvisionCmd ("/nosql/new" , "nosql" ),
76+ Use : "new --name <name>" ,
77+ Short : "Provision a MongoDB document store" ,
78+ Example : " instant nosql new --name app-docs" ,
79+ Args : cobra .NoArgs ,
80+ RunE : makeProvisionCmd ("/nosql/new" , "nosql" ),
4881}
4982
5083var queueNewCmd = & cobra.Command {
51- Use : "new [name]" ,
52- Short : "Provision a NATS JetStream queue" ,
53- Args : cobra .MaximumNArgs (1 ),
54- RunE : makeProvisionCmd ("/queue/new" , "queue" ),
84+ Use : "new --name <name>" ,
85+ Short : "Provision a NATS JetStream queue" ,
86+ Example : " instant queue new --name app-jobs" ,
87+ Args : cobra .NoArgs ,
88+ RunE : makeProvisionCmd ("/queue/new" , "queue" ),
5589}
5690
5791// makeProvisionCmd returns a RunE function that POSTs to the given endpoint
58- // and prints the provisioned connection URL.
92+ // and prints the provisioned connection URL. The resource name comes from the
93+ // required --name flag.
5994func makeProvisionCmd (endpoint , resourceType string ) func (* cobra.Command , []string ) error {
6095 return func (cmd * cobra.Command , args []string ) error {
61- name := resourceType
62- if len ( args ) == 1 {
63- name = args [ 0 ]
96+ name := resourceName
97+ if err := validateResourceName ( name ); err != nil {
98+ return err
6499 }
65100
66101 creds , err := provisionResource (endpoint , name )
@@ -137,10 +172,10 @@ var statusCmd = &cobra.Command{
137172 Long : `Display all resources saved in ~/.instant-tokens.
138173
139174Resources are saved automatically when you run:
140- instant db new
141- instant cache new
142- instant nosql new
143- instant queue new
175+ instant db new --name <name>
176+ instant cache new --name <name>
177+ instant nosql new --name <name>
178+ instant queue new --name <name>
144179` ,
145180 RunE : func (cmd * cobra.Command , args []string ) error {
146181 store , err := tokens .Load ()
@@ -170,6 +205,13 @@ Resources are saved automatically when you run:
170205}
171206
172207func init () {
208+ // --name is REQUIRED on every provisioning command. Cobra surfaces a
209+ // clear `required flag(s) "name" not set` error before RunE runs.
210+ for _ , c := range []* cobra.Command {dbNewCmd , cacheNewCmd , nosqlNewCmd , queueNewCmd } {
211+ c .Flags ().StringVar (& resourceName , "name" , "" , "Resource name (required, 1–64 chars, matches ^[A-Za-z0-9][A-Za-z0-9 _-]*$)" )
212+ _ = c .MarkFlagRequired ("name" )
213+ }
214+
173215 dbCmd .AddCommand (dbNewCmd )
174216 cacheCmd .AddCommand (cacheNewCmd )
175217 nosqlCmd .AddCommand (nosqlNewCmd )
0 commit comments