Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
31 changes: 25 additions & 6 deletions apps/cli/src/legacy/commands/network-bans/get/get.command.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Effect } from "effect";
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 { legacyRequireExperimental } from "../../../shared/legacy-experimental-gate.ts";
import { LEGACY_RESOURCE_OUTPUT_FORMATS } from "../../../shared/legacy-go-output-flag.ts";
import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts";
import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts";
import {
legacyValidateOutputFormat,
withLegacyCommandInstrumentation,
} from "../../../telemetry/legacy-command-instrumentation.ts";
import { legacyNetworkBansGet } from "./get.handler.ts";

const config = {
Expand All @@ -19,10 +25,23 @@ export const legacyNetworkBansGetCommand = Command.make("get", config).pipe(
Command.withDescription("Get the current network bans."),
Command.withShortDescription("Get the current network bans"),
Command.withHandler((flags) =>
legacyNetworkBansGet(flags).pipe(
withLegacyCommandInstrumentation({ flags }),
withJsonErrorHandling,
),
Effect.gen(function* () {
// Cobra parses flags — rejecting an out-of-enum `-o` (`internal/utils/enum.go:21-27`)
// — before `PersistentPreRunE` ever runs (`cobra@v1.10.2/command.go:919,985`), so an
// invalid `-o` value must win over a missing `--experimental` flag.
yield* legacyValidateOutputFormat(LEGACY_RESOURCE_OUTPUT_FORMATS);
// Go gates `bansCmd` (network-bans) behind `--experimental` in PersistentPreRunE
// (root.go:91-96) BEFORE the `IsManagementAPI` login check (root.go:105-109).
// `legacyManagementApiRuntimeLayer` eagerly resolves an access token as part
// of building its `LegacyPlatformApi` layer, so it must be provided AFTER
// the gate (inline here) rather than via `Command.provide` on the whole
// command — `Command.provide` would build the layer, and fail on a missing
// token, before this generator's first `yield*` ever runs.
yield* legacyRequireExperimental;
return yield* legacyNetworkBansGet(flags).pipe(
withLegacyCommandInstrumentation({ flags }),
Effect.provide(legacyManagementApiRuntimeLayer(["network-bans", "get"])),
);
}).pipe(withJsonErrorHandling),
),
Command.provide(legacyManagementApiRuntimeLayer(["network-bans", "get"])),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { describe, expect, it } from "@effect/vitest";
import { Effect, Exit, Layer } from "effect";
import { CliOutput, Command } from "effect/unstable/cli";

import { textCliOutputFormatter } from "../../../shared/output/text-formatter.ts";
import { LEGACY_GLOBAL_FLAGS } from "../../../shared/legacy/global-flags.ts";
import { TelemetryRuntime } from "../../../shared/telemetry/runtime.service.ts";
import { makeTelemetryIdentity } from "../../../shared/telemetry/identity.ts";
import { mockOutput, processEnvLayer } from "../../../../tests/helpers/mocks.ts";
import {
buildLegacyTestRuntime,
mockLegacyCliConfig,
mockLegacyPlatformApi,
useLegacyTempWorkdir,
} from "../../../../tests/helpers/legacy-mocks.ts";
import { legacyNetworkBansCommand } from "./network-bans.command.ts";

// See postgres-config.experimental-gate.integration.test.ts for the full
// rationale: this proves `--experimental` is wired into the actual
// `.command.ts` handler pipeline AND runs before
// `legacyManagementApiRuntimeLayer`'s eager access-token resolution
// (Go's `IsExperimental` check precedes `IsManagementAPI` in
// `apps/cli-go/cmd/root.go:91-109`).

const tempRoot = useLegacyTempWorkdir("supabase-network-bans-experimental-int-");

const testRoot = Command.make("supabase").pipe(
Command.withGlobalFlags(LEGACY_GLOBAL_FLAGS),
Command.withSubcommands([legacyNetworkBansCommand]),
);

function setup() {
const out = mockOutput({ format: "text" });
const api = mockLegacyPlatformApi({
response: { status: 200, body: { banned_ipv4_addresses: [] } },
});
const runtime = buildLegacyTestRuntime({
out,
api,
cliConfig: mockLegacyCliConfig({ workdir: tempRoot.current }),
});
const layer = Layer.mergeAll(
runtime,
CliOutput.layer(textCliOutputFormatter()),
// The "gate open" case reaches the real `legacyManagementApiRuntimeLayer`
// (provided inline inside the command, not by this test's mocked runtime),
// which reads credentials/env directly — an ambient SUPABASE_ACCESS_TOKEN,
// SUPABASE_EXPERIMENTAL, or OS keyring entry on the machine running the
// test would make these assertions non-deterministic. Wipe process.env
// down to just this and disable the keyring fallback.
processEnvLayer({ SUPABASE_NO_KEYRING: "1" }),
Layer.succeed(
TelemetryRuntime,
TelemetryRuntime.of({
configDir: `${tempRoot.current}/.supabase`,
tracesDir: `${tempRoot.current}/.supabase/traces`,
consent: "granted",
showDebug: false,
deviceId: "test-device-id",
sessionId: "test-session-id",
identity: makeTelemetryIdentity(undefined),
isFirstRun: false,
isTty: false,
isCi: false,
os: "linux",
arch: "x64",
cliVersion: "0.1.0",
}),
),
);
return { layer, api };
}

describe("legacy network-bans experimental gate (Go PersistentPreRunE parity)", () => {
const leaves: ReadonlyArray<{ readonly name: string; readonly args: ReadonlyArray<string> }> = [
{ name: "get", args: ["network-bans", "get"] },
{ name: "remove", args: ["network-bans", "remove"] },
];

for (const { name, args } of leaves) {
it.live(
`${name} fails with LegacyExperimentalRequiredError when --experimental is unset`,
() => {
const { layer, api } = setup();
return Effect.gen(function* () {
const exit = yield* Effect.exit(
Command.runWith(testRoot, { version: "0.0.0-test" })(args),
);
expect(Exit.isFailure(exit)).toBe(true);
if (Exit.isFailure(exit)) {
expect(JSON.stringify(exit.cause)).toContain("LegacyExperimentalRequiredError");
}
expect(api.requests).toHaveLength(0);
}).pipe(Effect.provide(layer));
},
);

it.live(`${name} does not fail with the gate error once --experimental is set`, () => {
const { layer, api } = setup();
return Effect.gen(function* () {
const exit = yield* Effect.exit(
Command.runWith(testRoot, { version: "0.0.0-test" })([...args, "--experimental"]),
);
expect(Exit.isFailure(exit)).toBe(true);
if (Exit.isFailure(exit)) {
const causeText = JSON.stringify(exit.cause);
expect(causeText).not.toContain("LegacyExperimentalRequiredError");
expect(causeText).toContain("LegacyPlatformAuthRequiredError");
}
expect(api.requests).toHaveLength(0);
}).pipe(Effect.provide(layer));
});
}
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Effect } from "effect";
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 { legacyRequireExperimental } from "../../../shared/legacy-experimental-gate.ts";
import { LEGACY_RESOURCE_OUTPUT_FORMATS } from "../../../shared/legacy-go-output-flag.ts";
import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts";
import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts";
import {
legacyValidateOutputFormat,
withLegacyCommandInstrumentation,
} from "../../../telemetry/legacy-command-instrumentation.ts";
import { legacyNetworkBansRemove } from "./remove.handler.ts";

const config = {
Expand All @@ -23,10 +29,23 @@ export const legacyNetworkBansRemoveCommand = Command.make("remove", config).pip
Command.withDescription("Remove a network ban."),
Command.withShortDescription("Remove a network ban"),
Command.withHandler((flags) =>
legacyNetworkBansRemove(flags).pipe(
withLegacyCommandInstrumentation({ flags }),
withJsonErrorHandling,
),
Effect.gen(function* () {
// Cobra parses flags — rejecting an out-of-enum `-o` (`internal/utils/enum.go:21-27`)
// — before `PersistentPreRunE` ever runs (`cobra@v1.10.2/command.go:919,985`), so an
// invalid `-o` value must win over a missing `--experimental` flag.
yield* legacyValidateOutputFormat(LEGACY_RESOURCE_OUTPUT_FORMATS);
// Go gates `bansCmd` (network-bans) behind `--experimental` in PersistentPreRunE
// (root.go:91-96) BEFORE the `IsManagementAPI` login check (root.go:105-109).
// `legacyManagementApiRuntimeLayer` eagerly resolves an access token as part
// of building its `LegacyPlatformApi` layer, so it must be provided AFTER
// the gate (inline here) rather than via `Command.provide` on the whole
// command — `Command.provide` would build the layer, and fail on a missing
// token, before this generator's first `yield*` ever runs.
yield* legacyRequireExperimental;
return yield* legacyNetworkBansRemove(flags).pipe(
withLegacyCommandInstrumentation({ flags }),
Effect.provide(legacyManagementApiRuntimeLayer(["network-bans", "remove"])),
);
}).pipe(withJsonErrorHandling),
),
Command.provide(legacyManagementApiRuntimeLayer(["network-bans", "remove"])),
);
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { Effect } from "effect";
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 { legacyRequireExperimental } from "../../../shared/legacy-experimental-gate.ts";
import { LEGACY_RESOURCE_OUTPUT_FORMATS } from "../../../shared/legacy-go-output-flag.ts";
import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts";
import { legacyParseStringSliceFlag } from "../../../shared/legacy-string-slice-flag.ts";
import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts";
import {
legacyValidateOutputFormat,
withLegacyCommandInstrumentation,
} from "../../../telemetry/legacy-command-instrumentation.ts";
import { legacyPostgresConfigDelete } from "./delete.handler.ts";

export const legacyPostgresConfigDeleteConfigFlag = Flag.string("config").pipe(
Expand Down Expand Up @@ -33,10 +39,23 @@ export const legacyPostgresConfigDeleteCommand = Command.make("delete", config).
Command.withDescription("Delete specific Postgres database config overrides."),
Command.withShortDescription("Delete Postgres database config overrides"),
Command.withHandler((flags) =>
legacyPostgresConfigDelete(flags).pipe(
withLegacyCommandInstrumentation({ flags }),
withJsonErrorHandling,
),
Effect.gen(function* () {
// Cobra parses flags — rejecting an out-of-enum `-o` (`internal/utils/enum.go:21-27`)
// — before `PersistentPreRunE` ever runs (`cobra@v1.10.2/command.go:919,985`), so an
// invalid `-o` value must win over a missing `--experimental` flag.
yield* legacyValidateOutputFormat(LEGACY_RESOURCE_OUTPUT_FORMATS);
// Go gates `postgresCmd` behind `--experimental` in PersistentPreRunE
// (root.go:91-96) BEFORE the `IsManagementAPI` login check (root.go:105-109).
// `legacyManagementApiRuntimeLayer` eagerly resolves an access token as part
// of building its `LegacyPlatformApi` layer, so it must be provided AFTER
// the gate (inline here) rather than via `Command.provide` on the whole
// command — `Command.provide` would build the layer, and fail on a missing
// token, before this generator's first `yield*` ever runs.
yield* legacyRequireExperimental;
return yield* legacyPostgresConfigDelete(flags).pipe(
withLegacyCommandInstrumentation({ flags }),
Effect.provide(legacyManagementApiRuntimeLayer(["postgres-config", "delete"])),
);
}).pipe(withJsonErrorHandling),
),
Command.provide(legacyManagementApiRuntimeLayer(["postgres-config", "delete"])),
);
31 changes: 25 additions & 6 deletions apps/cli/src/legacy/commands/postgres-config/get/get.command.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Effect } from "effect";
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 { legacyRequireExperimental } from "../../../shared/legacy-experimental-gate.ts";
import { LEGACY_RESOURCE_OUTPUT_FORMATS } from "../../../shared/legacy-go-output-flag.ts";
import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts";
import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts";
import {
legacyValidateOutputFormat,
withLegacyCommandInstrumentation,
} from "../../../telemetry/legacy-command-instrumentation.ts";
import { legacyPostgresConfigGet } from "./get.handler.ts";

const config = {
Expand All @@ -19,10 +25,23 @@ export const legacyPostgresConfigGetCommand = Command.make("get", config).pipe(
Command.withDescription("Get the current Postgres database config overrides."),
Command.withShortDescription("Get Postgres database config"),
Command.withHandler((flags) =>
legacyPostgresConfigGet(flags).pipe(
withLegacyCommandInstrumentation({ flags }),
withJsonErrorHandling,
),
Effect.gen(function* () {
// Cobra parses flags — rejecting an out-of-enum `-o` (`internal/utils/enum.go:21-27`)
// — before `PersistentPreRunE` ever runs (`cobra@v1.10.2/command.go:919,985`), so an
// invalid `-o` value must win over a missing `--experimental` flag.
yield* legacyValidateOutputFormat(LEGACY_RESOURCE_OUTPUT_FORMATS);
// Go gates `postgresCmd` behind `--experimental` in PersistentPreRunE
// (root.go:91-96) BEFORE the `IsManagementAPI` login check (root.go:105-109).
// `legacyManagementApiRuntimeLayer` eagerly resolves an access token as part
// of building its `LegacyPlatformApi` layer, so it must be provided AFTER
// the gate (inline here) rather than via `Command.provide` on the whole
// command — `Command.provide` would build the layer, and fail on a missing
// token, before this generator's first `yield*` ever runs.
yield* legacyRequireExperimental;
Comment thread
Coly010 marked this conversation as resolved.
Comment thread
Coly010 marked this conversation as resolved.
return yield* legacyPostgresConfigGet(flags).pipe(
withLegacyCommandInstrumentation({ flags }),
Effect.provide(legacyManagementApiRuntimeLayer(["postgres-config", "get"])),
);
}).pipe(withJsonErrorHandling),
),
Command.provide(legacyManagementApiRuntimeLayer(["postgres-config", "get"])),
);
Loading