diff --git a/apps/cli-go/api/overlay.yaml b/apps/cli-go/api/overlay.yaml index b59831268b..4bdeba0d16 100644 --- a/apps/cli-go/api/overlay.yaml +++ b/apps/cli-go/api/overlay.yaml @@ -48,3 +48,9 @@ actions: update: schema: type: string +- target: $.components.schemas.V1CreateProjectBody.properties + description: Adds high availability create-project flag hidden from the generated NestJS spec + update: + high_availability: + type: boolean + description: Whether to enable high availability for the project. diff --git a/apps/cli-go/cmd/projects.go b/apps/cli-go/cmd/projects.go index 7b9976f136..316f924173 100644 --- a/apps/cli-go/cmd/projects.go +++ b/apps/cli-go/cmd/projects.go @@ -24,11 +24,12 @@ var ( Short: "Manage Supabase projects", } - interactive bool - projectName string - orgId string - dbPassword string - region = utils.EnumFlag{ + interactive bool + projectName string + orgId string + dbPassword string + highAvailability bool + region = utils.EnumFlag{ Allowed: utils.AwsRegions(), } size = utils.EnumFlag{ @@ -81,6 +82,9 @@ var ( if cmd.Flags().Changed("size") { body.DesiredInstanceSize = (*api.V1CreateProjectBodyDesiredInstanceSize)(&size.Value) } + if cmd.Flags().Changed("high-availability") { + body.HighAvailability = cast.Ptr(highAvailability) + } return create.Run(cmd.Context(), body, afero.NewOsFs()) }, } @@ -137,6 +141,7 @@ func init() { markFlagTelemetrySafe(createFlags.Lookup("org-id")) createFlags.StringVar(&dbPassword, "db-password", "", "Database password of the project.") createFlags.Var(®ion, "region", "Select a region close to you for the best performance.") + createFlags.BoolVar(&highAvailability, "high-availability", false, "Enable high availability for the project.") createFlags.String("plan", "", "Select a plan that suits your needs.") cobra.CheckErr(createFlags.MarkHidden("plan")) createFlags.Var(&size, "size", "Select a desired instance size for your project.") diff --git a/apps/cli-go/internal/projects/create/create_test.go b/apps/cli-go/internal/projects/create/create_test.go index 4845224157..f07d430c56 100644 --- a/apps/cli-go/internal/projects/create/create_test.go +++ b/apps/cli-go/internal/projects/create/create_test.go @@ -48,6 +48,34 @@ func TestProjectCreateCommand(t *testing.T) { assert.Empty(t, apitest.ListUnmatchedRequests()) }) + t.Run("creates a new high availability project", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + // Setup valid access token + token := apitest.RandomAccessToken(t) + t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) + // Flush pending mocks after test execution + defer gock.OffAll() + highAvailabilityParams := params + highAvailabilityParams.HighAvailability = cast.Ptr(true) + gock.New(utils.DefaultApiHost). + Post("/v1/projects"). + MatchType("json"). + JSON(highAvailabilityParams). + Reply(201). + JSON(api.V1ProjectResponse{ + Id: apitest.RandomProjectRef(), + OrganizationSlug: highAvailabilityParams.OrganizationSlug, + Name: highAvailabilityParams.Name, + Region: string(*highAvailabilityParams.Region), + CreatedAt: "2022-04-25T02:14:55.906498Z", + }) + // Run test + assert.NoError(t, Run(context.Background(), highAvailabilityParams, fsys)) + // Validate api + assert.Empty(t, apitest.ListUnmatchedRequests()) + }) + t.Run("throws error on failure to load token", func(t *testing.T) { assert.Error(t, Run(context.Background(), params, afero.NewMemMapFs())) }) diff --git a/apps/cli-go/pkg/api/types.gen.go b/apps/cli-go/pkg/api/types.gen.go index c3287bc286..caa91140fa 100644 --- a/apps/cli-go/pkg/api/types.gen.go +++ b/apps/cli-go/pkg/api/types.gen.go @@ -4503,6 +4503,9 @@ type V1CreateProjectBody struct { // DesiredInstanceSize Desired instance size. Omit this field to always default to the smallest possible size. DesiredInstanceSize *V1CreateProjectBodyDesiredInstanceSize `json:"desired_instance_size,omitempty"` + // HighAvailability Whether to enable high availability for the project. + HighAvailability *bool `json:"high_availability,omitempty"` + // KpsEnabled This field is deprecated and is ignored in this request // Deprecated: KpsEnabled *bool `json:"kps_enabled,omitempty"` diff --git a/apps/cli/src/legacy/commands/projects/create/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/projects/create/SIDE_EFFECTS.md index 78ec97bd69..28df41cbf2 100644 --- a/apps/cli/src/legacy/commands/projects/create/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/projects/create/SIDE_EFFECTS.md @@ -14,9 +14,9 @@ ## API Routes -| Method | Path | Auth | Request body | Response (used fields) | -| ------ | -------------- | ------------ | --------------------------------------------------------------------------- | --------------------------------------------------- | -| `POST` | `/v1/projects` | Bearer token | `{name, organization_slug, db_pass, region, desired_instance_size?}` (JSON) | `{id, name, organization_slug, region, created_at}` | +| Method | Path | Auth | Request body | Response (used fields) | +| ------ | -------------- | ------------ | ----------------------------------------------------------------------------------------------- | --------------------------------------------------- | +| `POST` | `/v1/projects` | Bearer token | `{name, organization_slug, db_pass, region, desired_instance_size?, high_availability?}` (JSON) | `{id, name, organization_slug, region, created_at}` | ## Environment Variables @@ -38,15 +38,16 @@ ## Flags -| Flag | Type | Required (non-interactive) | Description | -| ---------------- | ------ | -------------------------- | ----------------------------------------------- | -| `[project name]` | arg | yes (non-interactive) | Name of the project (positional argument) | -| `--org-id` | string | yes (non-interactive) | Organization ID (slug) to create the project in | -| `--db-password` | string | yes (non-interactive) | Database password for the project | -| `--region` | enum | yes (non-interactive) | AWS region for the project | -| `--size` | enum | no | Desired instance size | -| `--interactive` | bool | no (default: true) | Enable interactive mode (hidden flag) | -| `--plan` | string | no | Plan selection (hidden flag) | +| Flag | Type | Required (non-interactive) | Description | +| --------------------- | ------ | -------------------------- | ----------------------------------------------- | +| `[project name]` | arg | yes (non-interactive) | Name of the project (positional argument) | +| `--org-id` | string | yes (non-interactive) | Organization ID (slug) to create the project in | +| `--db-password` | string | yes (non-interactive) | Database password for the project | +| `--region` | enum | yes (non-interactive) | AWS region for the project | +| `--size` | enum | no | Desired instance size | +| `--high-availability` | bool | no | Enable high availability for the project | +| `--interactive` | bool | no (default: true) | Enable interactive mode (hidden flag) | +| `--plan` | string | no | Plan selection (hidden flag) | ## Output @@ -83,4 +84,5 @@ One `result` event on success. - In non-interactive mode (when stdin is not a TTY or `--interactive=false`), all three flags and the positional project name argument are required. - The `--size` flag, when provided, sets the `desired_instance_size` field in the request body. +- The `--high-availability` flag, when provided, sets the `high_availability` field in the request body. - The `--plan` flag is hidden and reserved. diff --git a/apps/cli/src/legacy/commands/projects/create/create.command.ts b/apps/cli/src/legacy/commands/projects/create/create.command.ts index 3f75a95df9..ab1011b264 100644 --- a/apps/cli/src/legacy/commands/projects/create/create.command.ts +++ b/apps/cli/src/legacy/commands/projects/create/create.command.ts @@ -67,6 +67,10 @@ const config = { Flag.withDescription("Select a desired instance size for your project."), Flag.optional, ), + highAvailability: Flag.boolean("high-availability").pipe( + Flag.withDescription("Enable high availability for the project."), + Flag.optional, + ), interactive: withHidden( Flag.boolean("interactive").pipe( Flag.withDescription("Enables interactive mode."), diff --git a/apps/cli/src/legacy/commands/projects/create/create.handler.ts b/apps/cli/src/legacy/commands/projects/create/create.handler.ts index 8c1f0060f2..10dce62929 100644 --- a/apps/cli/src/legacy/commands/projects/create/create.handler.ts +++ b/apps/cli/src/legacy/commands/projects/create/create.handler.ts @@ -12,6 +12,8 @@ export const legacyProjectsCreate = Effect.fn("legacy.projects.create")(function if (Option.isSome(flags.dbPassword)) args.push("--db-password", flags.dbPassword.value); if (Option.isSome(flags.region)) args.push("--region", flags.region.value); if (Option.isSome(flags.size)) args.push("--size", flags.size.value); + if (Option.isSome(flags.highAvailability)) + args.push(`--high-availability=${flags.highAvailability.value ? "true" : "false"}`); if (Option.isSome(flags.interactive)) args.push(`--interactive=${flags.interactive.value ? "true" : "false"}`); if (Option.isSome(flags.plan)) args.push("--plan", flags.plan.value); diff --git a/apps/cli/src/legacy/commands/projects/create/create.integration.test.ts b/apps/cli/src/legacy/commands/projects/create/create.integration.test.ts new file mode 100644 index 0000000000..66febb7639 --- /dev/null +++ b/apps/cli/src/legacy/commands/projects/create/create.integration.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Layer, Option } from "effect"; + +import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; +import { legacyProjectsCreate } from "./create.handler.ts"; +import type { LegacyProjectsCreateFlags } from "./create.command.ts"; + +function setupLegacyProjectsCreate() { + const calls: Array> = []; + const layer = Layer.succeed(LegacyGoProxy, { + exec: (args) => + Effect.sync(() => { + calls.push([...args]); + }), + }); + + return { layer, calls }; +} + +const baseFlags: LegacyProjectsCreateFlags = { + name: Option.some("my-project"), + orgId: Option.some("cool-green-pqdr0qc"), + dbPassword: Option.some("redacted"), + region: Option.some("us-east-1"), + size: Option.none(), + highAvailability: Option.none(), + interactive: Option.none(), + plan: Option.none(), +}; + +describe("legacy projects create", () => { + it.live("forwards the high availability flag to the Go CLI", () => { + const { layer, calls } = setupLegacyProjectsCreate(); + return Effect.gen(function* () { + yield* legacyProjectsCreate({ + ...baseFlags, + highAvailability: Option.some(true), + }); + expect(calls).toEqual([ + [ + "projects", + "create", + "my-project", + "--org-id", + "cool-green-pqdr0qc", + "--db-password", + "redacted", + "--region", + "us-east-1", + "--high-availability=true", + ], + ]); + }).pipe(Effect.provide(layer)); + }); +});