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
4 changes: 2 additions & 2 deletions apps/cli/src/command-internal/db-pull-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export type { DbPullFlags };

// Established output contract; ends with a `.`.
const DEPRECATION_LINE =
"Flag --use-pg-delta has been deprecated, use --declarative with [experimental.pgdelta] enabled = true in your config.toml instead.";
"Flag --use-pg-delta is deprecated and will be removed in v3, use --declarative with [experimental.pgdelta] enabled = true in your config.toml instead.";

/**
* Explains the in-sync non-zero exit instead of the generic "Try rerunning the command with
Expand All @@ -107,7 +107,7 @@ const MIGRATION_FILE_MODE = 0o644;
// export covers the same per-object-files outcome. Printed only when the experimental gate
// selected this branch, not when `--declarative` already did.
const EXPERIMENTAL_STRUCTURED_DUMP_DEPRECATION_LINE =
"The --experimental structured-dump mode for `db pull` is deprecated and will be removed in a future release. Use --declarative instead to pull the remote schema as per-object files.";
"The --experimental structured-dump mode for `db pull` is deprecated and will be removed in v3. Use --declarative instead to pull the remote schema as per-object files.";

export type DbPullInvoke = {
/** Skip printing `Finished supabase db pull.` for callers that must not emit it. */
Expand Down
8 changes: 4 additions & 4 deletions apps/cli/src/commands/db/pull/SIDE_EFFECTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ Progress to stderr. Migration path: `Creating shadow database...`,
`Diffing schemas[: <list>]`, `Schema written to <path>`. Declarative path:
`Preparing declarative schema export using pg-delta...`, `Declarative schema
written to <dir>`. Plus the `--use-pg-delta` deprecation line, the
`--experimental` structured-dump deprecation line, and the history-update
`--experimental` structured-dump deprecation line (both name v3), and the history-update
prompt. On success the PostRun line `Finished supabase db pull.` is printed to
stdout.

Expand All @@ -190,7 +190,7 @@ Progress strings still go to stderr; stdout carries a single structured envelope
env var simply leaves unused on a non-linked target). The deprecated
`--experimental` export honors `--project-ref` the same way `--declarative`
does.
- `--use-pg-delta` is hidden and emits the cobra deprecation line to stderr.
- `--use-pg-delta` is hidden and emits `Flag --use-pg-delta is deprecated and will be removed in v3, use --declarative with [experimental.pgdelta] enabled = true in your config.toml instead.` to stderr.
- Migration-style pulls always compare migrations with the live target;
declarative files and `schema_paths` do not replace that baseline.
- Bundled nontransactional files begin with
Expand All @@ -201,8 +201,8 @@ Progress strings still go to stderr; stdout carries a single structured envelope
empty diff is "No schema changes found". Stack-backed pulls never take this path.
- The `--experimental` structured-dump branch (or the `SUPABASE_EXPERIMENTAL`
project-`.env` equivalent) now runs the same in-process declarative export
as `--declarative`. It is deprecated: a warning pointing at `--declarative`
prints to stderr before the export. `--experimental --declarative` does not
as `--declarative`. It is deprecated and will be removed in v3: a warning pointing
at `--declarative` prints to stderr before the export. `--experimental --declarative` does not
print that line. Output is the pg-delta declarative tree under
`supabase/schemas` (plus `.pgdelta-export.json`), not Go's former
`schemas/` + `cluster/` AST split.
14 changes: 9 additions & 5 deletions apps/cli/src/commands/db/pull/pull.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1040,7 +1040,9 @@ describe("db pull", () => {
const s = setup(tmp.current, { edgeStdout: EXPORT_JSON });
return Effect.gen(function* () {
yield* dbPull(flags({ usePgDelta: Option.some(true) }));
expect(streamText(s.out, "stderr")).toContain("Flag --use-pg-delta has been deprecated");
expect(streamText(s.out, "stderr")).toContain(
"Flag --use-pg-delta is deprecated and will be removed in v3, use --declarative with [experimental.pgdelta] enabled = true in your config.toml instead.\n",
);
const path = yield* Path.Path;
expect(streamText(s.out, "stderr")).toContain(
`Declarative schema written to ${path.join("supabase", "schemas")}\n`,
Expand Down Expand Up @@ -1763,7 +1765,7 @@ describe("db pull", () => {
expect(s.engineCalls[0]?.operation).toBe("export");
expect(streamText(s.out, "stderr")).toContain("Connecting to remote database...");
expect(streamText(s.out, "stderr")).toContain(
"The --experimental structured-dump mode for `db pull` is deprecated",
"The --experimental structured-dump mode for `db pull` is deprecated and will be removed in v3.",
);
expect(streamText(s.out, "stderr")).toContain("Preparing declarative schema export");
}).pipe(Effect.provide(s.layer));
Expand Down Expand Up @@ -1818,7 +1820,7 @@ describe("db pull", () => {
expect(s.engineCalls[0]?.operation).toBe("export");
expect(s.proxyCalls).toHaveLength(0);
expect(streamText(s.out, "stderr")).toContain(
"The --experimental structured-dump mode for `db pull` is deprecated",
"The --experimental structured-dump mode for `db pull` is deprecated and will be removed in v3.",
);
}).pipe(Effect.provide(s.layer));
});
Expand All @@ -1842,7 +1844,7 @@ describe("db pull", () => {
expect(s.engineCalls[0]?.operation).toBe("export");
expect(streamText(s.out, "stderr")).toContain("Connecting to remote database...");
expect(streamText(s.out, "stderr")).toContain(
"The --experimental structured-dump mode for `db pull` is deprecated",
"The --experimental structured-dump mode for `db pull` is deprecated and will be removed in v3.",
);
}).pipe(Effect.provide(s.layer));
},
Expand Down Expand Up @@ -2412,7 +2414,9 @@ describe("db remote commit", () => {
expect(err).toContain(
`Command "commit" is deprecated and will be removed in v3, use "db pull" instead.\n`,
);
expect(err).toContain("The --experimental structured-dump mode for `db pull` is deprecated");
expect(err).toContain(
"The --experimental structured-dump mode for `db pull` is deprecated and will be removed in v3.",
);
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
expect(
Expand Down
3 changes: 2 additions & 1 deletion apps/cli/src/commands/domains/SIDE_EFFECTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ suppressed on stderr. `delete` ignores `-o`.
- `create` validates the CNAME via Cloudflare DNS-over-HTTPS (`https://1.1.1.1`, 10s timeout) before initializing; on failure it short-circuits before any POST.
- All subcommands resolve the ref via `--project-ref` → `SUPABASE_PROJECT_ID` env → linked-project file.
- The project-ref fallback env var is `SUPABASE_PROJECT_ID`.
- `--include-raw-output` is deprecated: passing it with any value (including `--include-raw-output=false`) prints `Flag --include-raw-output is deprecated and will be removed in v3, use -o json instead.` to stderr before the API call, in every output mode, on all five subcommands (including `delete`). stdout is unaffected.
- **Documented divergences from the old Go CLI (intentional):**
- `--include-raw-output` is declared as a normal boolean **on each subcommand** (the old Go CLI declared it as a persistent flag on the `domains` group). Two consequences: (a) it must appear after the subcommand name (`domains get --include-raw-output`) rather than before it (`domains --include-raw-output get`), matching how `--project-ref` is already handled shell-wide; (b) it cannot reproduce the old help-hiding or the `Flag --include-raw-output has been deprecated` stderr warning, which Effect CLI has no hook for. It still reproduces the behavioral effect (forces `-o json` when `-o` is unset/pretty); on `delete` it is inert.
- `--include-raw-output` is declared as an optional boolean **on each subcommand** (the old Go CLI declared it as a persistent flag on the `domains` group). Two consequences: (a) it must appear after the subcommand name (`domains get --include-raw-output`) rather than before it (`domains --include-raw-output get`), matching how `--project-ref` is already handled shell-wide; (b) it cannot reproduce the old help-hiding. It still reproduces the behavioral effect (forces `-o json` when the value is `true` and `-o` is unset/pretty); on `delete` it is inert.
- The degenerate `validation_records != 1` status message approximates a Go-style `%+v` struct dump (which embeds a non-deterministic pointer address).
- Text-mode status output is newline-terminated even where the old CLI's output wasn't. Without the final newline, interactive shell prompts can redraw over the last status line, hiding the ACME TXT record.
- In a structured `-o` mode the human status is suppressed on stderr, keeping stdout clean and matching the parity contract.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const config = {
),
includeRawOutput: Flag.Boolean("include-raw-output").pipe(
Flag.withDescription("(Deprecated) use -o json instead."),
Flag.withDefault(false),
Flag.optional,
),
} as const;

Expand Down
10 changes: 8 additions & 2 deletions apps/cli/src/commands/domains/activate/activate.handler.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Effect } from "effect";
import { Effect, Option } from "effect";

import { CommandPlatformApi } from "../../../auth/command-platform-api.service.ts";
import { ProjectRefResolver } from "../../../config/project-ref.service.ts";
import { Output } from "../../../shared/output/output.service.ts";
import { LinkedProjectCache } from "../../../telemetry/linked-project-cache.service.ts";
import { TelemetryState } from "../../../telemetry/telemetry-state.service.ts";
import { warnIncludeRawOutputDeprecated } from "../domains.deprecation.ts";
import { emitHostnameResult } from "../domains.emit.ts";
import { mapDomainsHttpError } from "../domains.errors.ts";
import { gateMapError } from "../../../command-internal/upgrade-suggest.ts";
Expand All @@ -21,6 +22,8 @@ export const domainsActivate = Effect.fn("domains.activate")(function* (
const linkedProjectCache = yield* LinkedProjectCache;
const telemetryState = yield* TelemetryState;

yield* warnIncludeRawOutputDeprecated(flags.includeRawOutput);

const ref = yield* resolver.resolve(flags.projectRef);

yield* Effect.gen(function* () {
Expand All @@ -32,6 +35,9 @@ export const domainsActivate = Effect.fn("domains.activate")(function* (
);
yield* activating?.clear ?? Effect.void;

yield* emitHostnameResult(response, flags.includeRawOutput);
yield* emitHostnameResult(
response,
Option.getOrElse(flags.includeRawOutput, () => false),
);
}).pipe(Effect.ensuring(linkedProjectCache.cache(ref)), Effect.ensuring(telemetryState.flush));
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type V1GetHostnameConfigOutput } from "@supabase/api/effect";
import { describe, expect, it } from "@effect/vitest";
import { Cause, Effect, Exit, Option } from "effect";
import { Cause, Effect, Exit, Option, Schema } from "effect";

import { mockAnalytics, mockOutput } from "../../../../tests/helpers/mocks.ts";
import {
Expand Down Expand Up @@ -65,7 +65,9 @@ function setup(opts: SetupOpts = {}) {
return { layer, out, api, analytics, telemetry, linkedProjectCache };
}

const baseFlags = { projectRef: Option.none<string>(), includeRawOutput: false };
const baseFlags = { projectRef: Option.none<string>(), includeRawOutput: Option.none<boolean>() };

const DeprecationJsonObject = Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown));

describe("domains activate integration", () => {
it.effect("suggests upgrade from entitlement_required envelope on 400", () => {
Expand Down Expand Up @@ -127,11 +129,39 @@ describe("domains activate integration", () => {
}).pipe(Effect.provide(layer));
});

it.effect("forces Go JSON output when --include-raw-output is set", () => {
it.effect(
"warns on stderr and keeps stdout JSON parseable when --include-raw-output is set",
() => {
const { layer, out } = setup();
return Effect.gen(function* () {
yield* domainsActivate({ projectRef: Option.none(), includeRawOutput: Option.some(true) });
expect(out.stderrText).toBe(
"Flag --include-raw-output is deprecated and will be removed in v3, use -o json instead.\n",
);
const parsed = yield* Schema.decodeEffect(DeprecationJsonObject)(out.stdoutText);
expect(parsed.custom_hostname).toBe("shop.acme.dev");
}).pipe(Effect.provide(layer));
},
);

it.effect("warns but does not force JSON when --include-raw-output=false", () => {
const { layer, out } = setup();
return Effect.gen(function* () {
yield* domainsActivate({ projectRef: Option.none(), includeRawOutput: true });
expect(out.stdoutText.startsWith("{")).toBe(true);
yield* domainsActivate({ projectRef: Option.none(), includeRawOutput: Option.some(false) });
expect(
out.stderrText.startsWith(
"Flag --include-raw-output is deprecated and will be removed in v3, use -o json instead.\n",
),
).toBe(true);
expect(out.stdoutText).toBe("");
}).pipe(Effect.provide(layer));
});

it.effect("does not warn when --include-raw-output is not set", () => {
const { layer, out } = setup();
return Effect.gen(function* () {
yield* domainsActivate(baseFlags);
expect(out.stderrText).not.toContain("deprecated");
}).pipe(Effect.provide(layer));
});

Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/commands/domains/create/create.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const config = {
),
includeRawOutput: Flag.Boolean("include-raw-output").pipe(
Flag.withDescription("(Deprecated) use -o json instead."),
Flag.withDefault(false),
Flag.optional,
),
} as const;

Expand Down
10 changes: 8 additions & 2 deletions apps/cli/src/commands/domains/create/create.handler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Effect } from "effect";
import { Effect, Option } from "effect";
import * as HttpClient from "effect/http/HttpClient";

import { CommandPlatformApi } from "../../../auth/command-platform-api.service.ts";
Expand All @@ -8,6 +8,7 @@ import { Output } from "../../../shared/output/output.service.ts";
import { LinkedProjectCache } from "../../../telemetry/linked-project-cache.service.ts";
import { TelemetryState } from "../../../telemetry/telemetry-state.service.ts";
import { verifyCname } from "../domains.cname.ts";
import { warnIncludeRawOutputDeprecated } from "../domains.deprecation.ts";
import { emitHostnameResult } from "../domains.emit.ts";
import { mapDomainsHttpError } from "../domains.errors.ts";
import { gateMapError } from "../../../command-internal/upgrade-suggest.ts";
Expand All @@ -24,6 +25,8 @@ export const domainsCreate = Effect.fn("domains.create")(function* (flags: Domai
const linkedProjectCache = yield* LinkedProjectCache;
const telemetryState = yield* TelemetryState;

yield* warnIncludeRawOutputDeprecated(flags.includeRawOutput);

const ref = yield* resolver.resolve(flags.projectRef);

// Write the linked-project cache and persist the telemetry state file on
Expand All @@ -48,6 +51,9 @@ export const domainsCreate = Effect.fn("domains.create")(function* (flags: Domai
);
yield* creating?.clear ?? Effect.void;

yield* emitHostnameResult(response, flags.includeRawOutput);
yield* emitHostnameResult(
response,
Option.getOrElse(flags.includeRawOutput, () => false),
);
}).pipe(Effect.ensuring(linkedProjectCache.cache(ref)), Effect.ensuring(telemetryState.flush));
});
42 changes: 36 additions & 6 deletions apps/cli/src/commands/domains/create/create.integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type V1GetHostnameConfigOutput } from "@supabase/api/effect";
import { describe, expect, it } from "@effect/vitest";
import { Cause, Deferred, Effect, Exit, Fiber, Option } from "effect";
import { Cause, Deferred, Effect, Exit, Fiber, Option, Schema } from "effect";
import { TestClock } from "effect/testing";

import { mockAnalytics, mockOutput } from "../../../../tests/helpers/mocks.ts";
Expand Down Expand Up @@ -98,18 +98,20 @@ function setup(opts: SetupOpts = {}) {
return { layer, out, api, analytics, telemetry, linkedProjectCache };
}

function flags(over: Partial<{ includeRawOutput: boolean }> = {}) {
function flags(over: Partial<{ includeRawOutput: Option.Option<boolean> }> = {}) {
return {
projectRef: Option.none<string>(),
customHostname: CUSTOM_HOSTNAME,
includeRawOutput: over.includeRawOutput ?? false,
includeRawOutput: over.includeRawOutput ?? Option.none<boolean>(),
};
}

function postedToInitialize(api: { requests: ReadonlyArray<{ url: string }> }): boolean {
return api.requests.some((r) => r.url.includes("/custom-hostname/initialize"));
}

const DeprecationJsonObject = Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown));

describe("domains create integration", () => {
it.effect("verifies the CNAME, creates the hostname, and prints status to stderr", () => {
const { layer, out, api, telemetry, linkedProjectCache } = setup();
Expand Down Expand Up @@ -262,11 +264,39 @@ describe("domains create integration", () => {
}).pipe(Effect.provide(layer));
});

it.effect("forces Go JSON output when --include-raw-output is set", () => {
it.effect(
"warns on stderr and keeps stdout JSON parseable when --include-raw-output is set",
() => {
const { layer, out } = setup();
return Effect.gen(function* () {
yield* domainsCreate(flags({ includeRawOutput: Option.some(true) }));
expect(out.stderrText).toBe(
"Flag --include-raw-output is deprecated and will be removed in v3, use -o json instead.\n",
);
const parsed = yield* Schema.decodeEffect(DeprecationJsonObject)(out.stdoutText);
expect(parsed.custom_hostname).toBe(CUSTOM_HOSTNAME);
}).pipe(Effect.provide(layer));
},
);

it.effect("warns but does not force JSON when --include-raw-output=false", () => {
const { layer, out } = setup();
return Effect.gen(function* () {
yield* domainsCreate(flags({ includeRawOutput: true }));
expect(out.stdoutText.startsWith("{")).toBe(true);
yield* domainsCreate(flags({ includeRawOutput: Option.some(false) }));
expect(
out.stderrText.startsWith(
"Flag --include-raw-output is deprecated and will be removed in v3, use -o json instead.\n",
),
).toBe(true);
expect(out.stdoutText).toBe("");
}).pipe(Effect.provide(layer));
});

it.effect("does not warn when --include-raw-output is not set", () => {
const { layer, out } = setup();
return Effect.gen(function* () {
yield* domainsCreate(flags());
expect(out.stderrText).not.toContain("deprecated");
}).pipe(Effect.provide(layer));
});

Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/commands/domains/delete/delete.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const config = {
),
includeRawOutput: Flag.Boolean("include-raw-output").pipe(
Flag.withDescription("(Deprecated) use -o json instead."),
Flag.withDefault(false),
Flag.optional,
),
} as const;

Expand Down
8 changes: 5 additions & 3 deletions apps/cli/src/commands/domains/delete/delete.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,25 @@ import { ProjectRefResolver } from "../../../config/project-ref.service.ts";
import { Output } from "../../../shared/output/output.service.ts";
import { LinkedProjectCache } from "../../../telemetry/linked-project-cache.service.ts";
import { TelemetryState } from "../../../telemetry/telemetry-state.service.ts";
import { warnIncludeRawOutputDeprecated } from "../domains.deprecation.ts";
import { mapDomainsHttpError } from "../domains.errors.ts";
import type { DomainsDeleteFlags } from "./delete.command.ts";

const mapDeleteError = mapDomainsHttpError("delete");

const DELETE_SUCCESS_MESSAGE = "Deleted custom hostname config successfully.";

// `flags.includeRawOutput` is unread: `--include-raw-output` is a persistent flag on the
// `domains` group, so it's accepted on `delete` too, but ignored — delete has no response
// body to encode. Asserted by the "ignores --include-raw-output" integration test.
// `--include-raw-output` is accepted on `delete` but inert (only the deprecation warning fires):
// delete has no response body to encode.
export const domainsDelete = Effect.fn("domains.delete")(function* (flags: DomainsDeleteFlags) {
const output = yield* Output;
const api = yield* CommandPlatformApi;
const resolver = yield* ProjectRefResolver;
const linkedProjectCache = yield* LinkedProjectCache;
const telemetryState = yield* TelemetryState;

yield* warnIncludeRawOutputDeprecated(flags.includeRawOutput);

const ref = yield* resolver.resolve(flags.projectRef);

yield* Effect.gen(function* () {
Expand Down
Loading
Loading