Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/cli/docs/go-cli-porting-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,10 @@ Legend:
| `domains reverify` | `wrapped` | [`../src/legacy/commands/domains/reverify/reverify.command.ts`](../src/legacy/commands/domains/reverify/reverify.command.ts) |
| `domains activate` | `wrapped` | [`../src/legacy/commands/domains/activate/activate.command.ts`](../src/legacy/commands/domains/activate/activate.command.ts) |
| `domains delete` | `wrapped` | [`../src/legacy/commands/domains/delete/delete.command.ts`](../src/legacy/commands/domains/delete/delete.command.ts) |
| `vanity-subdomains get` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/get/get.command.ts`](../src/legacy/commands/vanity-subdomains/get/get.command.ts) |
| `vanity-subdomains check-availability` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts`](../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts) |
| `vanity-subdomains activate` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/activate/activate.command.ts`](../src/legacy/commands/vanity-subdomains/activate/activate.command.ts) |
| `vanity-subdomains delete` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/delete/delete.command.ts`](../src/legacy/commands/vanity-subdomains/delete/delete.command.ts) |
| `vanity-subdomains get` | `ported` | [`../src/legacy/commands/vanity-subdomains/get/get.command.ts`](../src/legacy/commands/vanity-subdomains/get/get.command.ts) |
| `vanity-subdomains check-availability` | `ported` | [`../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts`](../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts) |
| `vanity-subdomains activate` | `ported` | [`../src/legacy/commands/vanity-subdomains/activate/activate.command.ts`](../src/legacy/commands/vanity-subdomains/activate/activate.command.ts) |
| `vanity-subdomains delete` | `ported` | [`../src/legacy/commands/vanity-subdomains/delete/delete.command.ts`](../src/legacy/commands/vanity-subdomains/delete/delete.command.ts) |
| `network-bans get` | `ported` | [`../src/legacy/commands/network-bans/get/get.command.ts`](../src/legacy/commands/network-bans/get/get.command.ts) |
| `network-bans remove` | `ported` | [`../src/legacy/commands/network-bans/remove/remove.command.ts`](../src/legacy/commands/network-bans/remove/remove.command.ts) |
| `network-restrictions get` | `ported` | [`../src/legacy/commands/network-restrictions/get/get.command.ts`](../src/legacy/commands/network-restrictions/get/get.command.ts) |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,72 @@

## Files Read

| Path | Format | When |
| -------------------------- | ------------------------- | ---------------------------------------------------------- |
| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable |
| Path | Format | When |
| -------------------------------------- | ------------------------- | ------------------------------------------------------------- |
| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable |
| `<workdir>/supabase/.temp/project-ref` | plain text (project ref) | when `--project-ref` flag and `PROJECT_ID` env are both unset |

## Files Written

| Path | Format | When |
| ---- | ------ | ---- |
| — | — | — |
| Path | Format | When |
| ------------------------------------------------ | ------ | ----------------------------------------------------- |
| `~/.supabase/<workdir-hash>/linked-project.json` | JSON | after ref resolution, on success and failure |
| `~/.supabase/telemetry.json` | JSON | always, via `Effect.ensuring`, on success and failure |

## API Routes

| Method | Path | Auth | Request body | Response (used fields) |
| ------ | ---------------------------------------------- | ------------ | ---------------------------- | ---------------------- |
| `POST` | `/v1/projects/{ref}/vanity-subdomain/activate` | Bearer token | `{vanity_subdomain: string}` | `{subdomain, status}` |
| Method | Path | Auth | Request body | Response (used fields) |
| ------ | ---------------------------------------------- | ------------ | ------------------------------ | --------------------------- |
| `POST` | `/v1/projects/{ref}/vanity-subdomain/activate` | Bearer token | `{ vanity_subdomain: string }` | `{ custom_domain: string }` |

## Environment Variables

| Variable | Purpose | Required? |
| ----------------------- | ---------------------------------------------------- | ------------------------------------------------------- |
| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring → `~/.supabase/access-token`) |
| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) |
| Variable | Purpose | Required? |
| ----------------------- | ---------------------------------------------------- | ---------------------------------------------------------- |
| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring then `~/.supabase/access-token`) |
| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) |
| `PROJECT_ID` | project ref fallback when `--project-ref` is unset | no (falls back to `supabase/.temp/project-ref`) |

## Exit Codes

| Code | Condition |
| ---- | --------------------------------------------------- |
| `0` | success — vanity subdomain activated |
| `1` | authentication error — no valid token found |
| `1` | API error — non-2xx response from activate endpoint |
| `1` | network / connection failure |
| Code | Condition |
| ---- | --------------------------------------------------------------------------------------- |
| `0` | success |
| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) |
| `1` | API non-2xx (`LegacyVanitySubdomainsActivateUnexpectedStatusError`) |
| `1` | transport failure (`LegacyVanitySubdomainsActivateNetworkError`) |

## Telemetry Events Fired

| Event | When | Notable properties / groups |
| ----------------------- | ------------------------------------------ | ------------------------------------------ |
| `cli_command_executed` | post-run, success or failure (via wrapper) | `exit_code`, `duration_ms`, `flags` |
| `cli_upgrade_suggested` | gated 4xx responses only | `feature_key=vanity_subdomain`, `org_slug` |

## Output

### `--output-format text` (Go CLI compatible)
### `--output-format text` / legacy `--output pretty`

Prints:

```text
Activated vanity subdomain at <custom_domain>
```

### Legacy `--output {json,yaml,toml,env}`

Prints activation result to stdout.
Encodes the response object directly.

### `--output-format json`

Single JSON object emitted to stdout on success.
Single structured success event with the full response object.

### `--output-format stream-json`

One `result` event on success.

```ndjson
{"type":"result","data":{...}}
```
One `result` event with the full response object.

## Notes

- Requires `--desired-subdomain` flag (mandatory).
- Requires `--project-ref` or a linked project (`.supabase/config.json`).
- After activation, the project's auth services will no longer function on the `{project-ref}.{supabase-domain}` hostname.
- The legacy `--output` flag wins over TS `--output-format` when both are provided.
- `linked-project.json` is written after ref resolution, even when the API call fails.
- On gated 4xx responses this command prints an upgrade suggestion and fires `cli_upgrade_suggested`.
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Command, Flag } from "effect/unstable/cli";
import type * as CliCommand from "effect/unstable/cli/Command";

import { withJsonErrorHandling } from "../../../../shared/output/json-error-handling.ts";
import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts";
import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts";
import { legacyVanitySubdomainsActivate } from "./activate.handler.ts";

const config = {
Expand All @@ -19,5 +23,11 @@ export const legacyVanitySubdomainsActivateCommand = Command.make("activate", co
"Activate a vanity subdomain for your Supabase project. This reconfigures your Supabase project to respond to requests on your vanity subdomain. After the vanity subdomain is activated, your project's auth services will no longer function on the {project-ref}.{supabase-domain} hostname.",
),
Command.withShortDescription("Activate a vanity subdomain"),
Command.withHandler((flags) => legacyVanitySubdomainsActivate(flags)),
Command.withHandler((flags) =>
legacyVanitySubdomainsActivate(flags).pipe(
withLegacyCommandInstrumentation({ flags }),
withJsonErrorHandling,
),
),
Command.provide(legacyManagementApiRuntimeLayer(["vanity-subdomains", "activate"])),
);
Original file line number Diff line number Diff line change
@@ -1,13 +1,100 @@
import { Effect, Option } from "effect";
import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts";

import { LegacyPlatformApi } from "../../../auth/legacy-platform-api.service.ts";
import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts";
import { legacySuggestUpgrade } from "../../../shared/legacy-upgrade-suggest.ts";
import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts";
import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts";
import { LegacyOutputFlag } from "../../../../shared/legacy/global-flags.ts";
import { Output } from "../../../../shared/output/output.service.ts";
import {
encodeEnv,
encodeGoJson,
encodeToml,
encodeYaml,
} from "../../../shared/legacy-go-output.encoders.ts";
import { mapLegacyHttpError } from "../../../shared/legacy-http-errors.ts";
import {
LegacyVanitySubdomainsActivateNetworkError,
LegacyVanitySubdomainsActivateUnexpectedStatusError,
} from "../vanity-subdomains.errors.ts";
import type { LegacyVanitySubdomainsActivateFlags } from "./activate.command.ts";

const mapActivateError = mapLegacyHttpError({
networkError: LegacyVanitySubdomainsActivateNetworkError,
statusError: LegacyVanitySubdomainsActivateUnexpectedStatusError,
networkMessage: (cause) => `failed activate vanity subdomain: ${cause}`,
statusMessage: (status, body) => `unexpected activate vanity subdomain status ${status}: ${body}`,
});

export const legacyVanitySubdomainsActivate = Effect.fn("legacy.vanity-subdomains.activate")(
function* (flags: LegacyVanitySubdomainsActivateFlags) {
const proxy = yield* LegacyGoProxy;
const args: string[] = ["vanity-subdomains", "activate"];
if (Option.isSome(flags.projectRef)) args.push("--project-ref", flags.projectRef.value);
args.push("--desired-subdomain", flags.desiredSubdomain);
yield* proxy.exec(args);
const output = yield* Output;
const legacyOutputFlag = yield* LegacyOutputFlag;
const api = yield* LegacyPlatformApi;
const resolver = yield* LegacyProjectRefResolver;
const linkedProjectCache = yield* LegacyLinkedProjectCache;
const telemetryState = yield* LegacyTelemetryState;

yield* Effect.gen(function* () {
const ref = yield* resolver.resolve(flags.projectRef);

yield* Effect.gen(function* () {
const activating =
output.format === "text"
? yield* output.task("Activating vanity subdomain...")
: undefined;
const response = yield* api.v1
.activateVanitySubdomainConfig({
ref,
vanity_subdomain: flags.desiredSubdomain,
})
.pipe(
Effect.tapError(() => activating?.fail() ?? Effect.void),
Effect.catch((cause) =>
Effect.gen(function* () {
// Flip the always-failing mapper into a success so we can inspect the
// tagged error before deciding whether to suggest an upgrade, then re-fail.
const mapped = yield* Effect.flip(mapActivateError(cause));
if (mapped._tag === "LegacyVanitySubdomainsActivateUnexpectedStatusError") {
yield* legacySuggestUpgrade({
projectRef: ref,
featureKey: "vanity_subdomain",
statusCode: mapped.status,
});
}
return yield* Effect.fail(mapped);
}),
),
);
yield* activating?.clear() ?? Effect.void;

const legacyOutput = Option.getOrUndefined(legacyOutputFlag);

if (legacyOutput === "json") {
yield* output.raw(encodeGoJson(response));
return;
}
if (legacyOutput === "yaml") {
yield* output.raw(encodeYaml(response));
return;
}
if (legacyOutput === "toml") {
yield* output.raw(encodeToml({ CustomDomain: response.custom_domain }) + "\n");
return;
}
if (legacyOutput === "env") {
yield* output.raw(encodeEnv(response) + "\n");
return;
}

if (output.format === "json" || output.format === "stream-json") {
yield* output.success("", response);
return;
}

yield* output.raw(`Activated vanity subdomain at ${response.custom_domain}\n`);
}).pipe(Effect.ensuring(linkedProjectCache.cache(ref)));
}).pipe(Effect.ensuring(telemetryState.flush));
},
);
Loading
Loading