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
23 changes: 1 addition & 22 deletions apps/cli/src/legacy/cli/agent-output.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,5 @@
import { describe, expect, test } from "vitest";
import { runSupabase } from "../../../tests/helpers/cli.ts";

function stripAnsi(output: string): string {
let stripped = "";
for (let i = 0; i < output.length; i++) {
const charCode = output.charCodeAt(i);
if (charCode !== 0x1b || output[i + 1] !== "[") {
stripped += output[i];
continue;
}

i += 2;
while (i < output.length) {
const code = output.charCodeAt(i);
if (code >= 0x40 && code <= 0x7e) {
break;
}
i++;
}
}
return stripped;
}
import { runSupabase, stripAnsi } from "../../../tests/helpers/cli.ts";

function parseJsonLines(output: string): Array<unknown> {
return stripAnsi(output)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { LegacyCliConfig } from "../../config/legacy-cli-config.service.ts";
import { LegacyLinkedProjectCache } from "../../telemetry/legacy-linked-project-cache.service.ts";
import { LegacyTelemetryState } from "../../telemetry/legacy-telemetry-state.service.ts";
import { LegacyWorkdirFlag, legacyResolveYes } from "../../../shared/legacy/global-flags.ts";
import { CONTEXT_CANCELED_MESSAGE } from "../../../shared/output/errors.ts";
import { Output } from "../../../shared/output/output.service.ts";
import { LegacyGoProxy } from "../../../shared/legacy/go-proxy.service.ts";
import { RuntimeInfo } from "../../../shared/runtime/runtime-info.service.ts";
Expand Down Expand Up @@ -135,7 +136,9 @@ export const legacyBootstrap = Effect.fn("legacy.bootstrap")(function* (
{ defaultValue: true },
);
if (!overwrite) {
return yield* new LegacyBootstrapOverwriteDeclinedError({ message: "context canceled" });
return yield* new LegacyBootstrapOverwriteDeclinedError({
message: CONTEXT_CANCELED_MESSAGE,
});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.ser
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 { CONTEXT_CANCELED_MESSAGE } from "../../../../shared/output/errors.ts";
import { Output } from "../../../../shared/output/output.service.ts";
import { detectGitBranch } from "../../../../shared/git/git-branch.ts";
import {
Expand Down Expand Up @@ -68,7 +69,7 @@ export const legacyBranchesCreate = Effect.fn("legacy.branches.create")(function
.promptConfirm(`Do you want to create a branch named ${gitBranch.value}?`)
.pipe(Effect.orElseSucceed(() => true));
if (!confirmed) {
return yield* new LegacyBranchesCreateCancelledError({ message: "context canceled" });
return yield* new LegacyBranchesCreateCancelledError({ message: CONTEXT_CANCELED_MESSAGE });
}
branchName = gitBranch.value;
if (gitBranchForBody === undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BunServices } from "@effect/platform-bun";
import { describe, expect, it } from "@effect/vitest";
import { Effect, Exit, Layer, Option } from "effect";

import { stripAnsi } from "../../../../../tests/helpers/ansi.ts";
import {
legacyFailWriteStringOnNthCallFsLayer,
mockLegacyCliConfig,
Expand Down Expand Up @@ -230,10 +231,6 @@ const flags = (over: Partial<LegacyDbDiffFlags> = {}): LegacyDbDiffFlags => ({
schema: over.schema ?? [],
});

// Strip ANSI so assertions are colour-independent: `legacyAqua`/`legacyYellow`
// emit colour only when the test runner's stderr is a TTY.
// eslint-disable-next-line no-control-regex
const stripAnsi = (text: string) => text.replace(/\x1b\[[0-9;]*m/gu, "");
const stdout = (out: ReturnType<typeof mockOutput>) =>
stripAnsi(
out.rawChunks
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";

import { stripAnsi } from "../../../../../tests/helpers/ansi.ts";
import {
legacyFormatByteSize,
legacyFormatCatalogSummary,
Expand All @@ -9,10 +10,6 @@ import {
legacySummarizeCatalogJson,
} from "./pull.debug.ts";

// ANSI may wrap the bold debugDir; strip for assertions.
// eslint-disable-next-line no-control-regex
const stripAnsi = (text: string) => text.replace(/\x1b\[[0-9;]*m/gu, "");

describe("legacyRedactPostgresURL", () => {
it("replaces the password but keeps the username", () => {
expect(legacyRedactPostgresURL("postgresql://postgres:secret@db.host:5432/postgres")).toBe(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BunServices } from "@effect/platform-bun";
import { describe, expect, it } from "@effect/vitest";
import { Effect, Exit, Layer, Option } from "effect";

import { stripAnsi } from "../../../../../tests/helpers/ansi.ts";
import {
legacyFailWriteStringOnNthCallFsLayer,
mockLegacyCliConfig,
Expand Down Expand Up @@ -304,8 +305,6 @@ const flags = (over: Partial<LegacyDbPullFlags> = {}): LegacyDbPullFlags => ({
password: over.password ?? Option.none(),
});

// eslint-disable-next-line no-control-regex
const stripAnsi = (text: string) => text.replace(/\x1b\[[0-9;]*m/gu, "");
const streamText = (out: ReturnType<typeof mockOutput>, stream: "stdout" | "stderr") =>
stripAnsi(
out.rawChunks
Expand Down
7 changes: 4 additions & 3 deletions apps/cli/src/legacy/commands/db/push/push.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Clock, Effect, FileSystem, Option, Path } from "effect";
import { CliArgs } from "../../../../shared/cli/cli-args.service.ts";
import { LegacyDnsResolverFlag } from "../../../../shared/legacy/global-flags.ts";
import { legacyResolveYesWithProjectEnv } from "../../../../shared/legacy/global-flags.ts";
import { CONTEXT_CANCELED_MESSAGE } from "../../../../shared/output/errors.ts";
import { Output } from "../../../../shared/output/output.service.ts";
import { LegacyCliConfig } from "../../../config/legacy-cli-config.service.ts";
import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts";
Expand Down Expand Up @@ -268,7 +269,7 @@ export const legacyDbPush = Effect.fn("legacy.db.push")(function* (flags: Legacy
);
if (!ok) {
return yield* Effect.fail(
new LegacyDbPushCancelledError({ message: "context canceled" }),
new LegacyDbPushCancelledError({ message: CONTEXT_CANCELED_MESSAGE }),
);
}
yield* legacySeedGlobals(
Expand All @@ -290,7 +291,7 @@ export const legacyDbPush = Effect.fn("legacy.db.push")(function* (flags: Legacy
);
if (!ok) {
return yield* Effect.fail(
new LegacyDbPushCancelledError({ message: "context canceled" }),
new LegacyDbPushCancelledError({ message: CONTEXT_CANCELED_MESSAGE }),
);
}
yield* legacyUpsertVaultSecrets(session, vaultSecrets);
Expand Down Expand Up @@ -333,7 +334,7 @@ export const legacyDbPush = Effect.fn("legacy.db.push")(function* (flags: Legacy
);
if (!ok) {
return yield* Effect.fail(
new LegacyDbPushCancelledError({ message: "context canceled" }),
new LegacyDbPushCancelledError({ message: CONTEXT_CANCELED_MESSAGE }),
);
}
yield* legacySeedData(session, fs, workdir, path, seeds, applyError);
Expand Down
44 changes: 44 additions & 0 deletions apps/cli/src/legacy/commands/db/reset/reset.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, test } from "vitest";

import { runSupabase, stripAnsi } from "../../../../../tests/helpers/cli.ts";

const E2E_TIMEOUT_MS = 30_000;

describe("supabase db reset (legacy)", () => {
let workdir: string;
beforeEach(() => {
workdir = mkdtempSync(join(tmpdir(), "sb-db-reset-e2e-"));
mkdirSync(join(workdir, "supabase"), { recursive: true });
writeFileSync(join(workdir, "supabase", "config.toml"), "[db]\nport = 54322\n");
});
afterEach(() => {
rmSync(workdir, { recursive: true, force: true });
});

// Docker-free: the destructive remote-reset confirmation fires after the config
// load and BEFORE any connection is dialed, so a piped decline exits without a
// database. Declining must byte-match Go: a single `context canceled` line on
// stderr and exit 1, with NO `--debug` troubleshooting hint — `recoverAndExit`
// skips `SuggestDebugFlag` for `context.Canceled` (apps/cli-go/cmd/root.go:287-303).
// CLI-1973.
test(
"declining the remote reset prompt prints only context canceled, no --debug hint",
{ timeout: E2E_TIMEOUT_MS },
async () => {
const { exitCode, stderr } = await runSupabase(
["db", "reset", "--db-url", "postgresql://postgres:postgres@127.0.0.1:9999/postgres"],
{ entrypoint: "legacy", cwd: workdir, stdin: "n\n" },
);
expect(exitCode).toBe(1);
// The destructive confirmation (default No → `[y/N]`) actually rendered and
// was answered — the cancellation didn't come from some other failure path.
expect(stripAnsi(stderr)).toContain("[y/N]");
const lines = stripAnsi(stderr).trimEnd().split("\n");
expect(lines.at(-1)).toBe("context canceled");
expect(stderr).not.toContain("Try rerunning the command with --debug");
},
);
});
5 changes: 4 additions & 1 deletion apps/cli/src/legacy/commands/db/reset/reset.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
legacyResolveYesWithProjectEnv,
} from "../../../../shared/legacy/global-flags.ts";
import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts";
import { CONTEXT_CANCELED_MESSAGE } from "../../../../shared/output/errors.ts";
import { Output } from "../../../../shared/output/output.service.ts";
import { legacyAqua, legacyYellow } from "../../../shared/legacy-colors.ts";
import { LegacyCliConfig } from "../../../config/legacy-cli-config.service.ts";
Expand Down Expand Up @@ -415,7 +416,9 @@ export const legacyDbReset = Effect.fn("legacy.db.reset")(function* (flags: Lega
false,
);
if (!shouldReset) {
return yield* Effect.fail(new LegacyDbResetCancelledError({ message: "context canceled" }));
return yield* Effect.fail(
new LegacyDbResetCancelledError({ message: CONTEXT_CANCELED_MESSAGE }),
);
}
yield* output.raw(`Resetting remote database${toLogMessage(resolvedVersion)}\n`, "stderr");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { Cause, Effect, Exit } from "effect";
import { describe, expect, it } from "vitest";

import { stripAnsi } from "../../../../../../tests/helpers/ansi.ts";
import { LegacyDeclarativeNotEnabledError } from "./declarative.errors.ts";
import {
legacyIsPgDeltaEnabled,
legacyPgDeltaSuggestion,
legacyRequirePgDelta,
} from "./declarative.gate.ts";

// `legacyAqua`/`legacyBold` colour their tokens when stderr is a TTY (matching
// Go's lipgloss). Strip ANSI so the assertions validate text content exactly,
// independent of the runner's colour profile.
const stripAnsi = (text: string) =>
text.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), "");

const EXPECTED_SUGGESTION =
"Either pass --experimental or add [experimental.pgdelta] with enabled = true to supabase/config.toml";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ describe("supabase gen signing-key (legacy)", () => {
});
expect(exitCode).toBe(1);
expect(stderr).toContain("context canceled");
// No SuggestDebugFlag fallback for context.Canceled (cmd/root.go:287-303, CLI-1973).
expect(stderr).not.toContain("Try rerunning the command with --debug");
expect(stderr).not.toContain("Service not found");
const saved = readFileSync(join(projectDir, "supabase", "signing_keys.json"), "utf8");
expect(JSON.parse(saved)).toEqual([]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { LegacyDebugLogger } from "../../../shared/legacy-debug-logger.service.t
import { legacyPromptYesNo } from "../../../shared/legacy-prompt-yes-no.ts";
import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts";
import { legacyResolveYesWithProjectEnv } from "../../../../shared/legacy/global-flags.ts";
import { CONTEXT_CANCELED_MESSAGE } from "../../../../shared/output/errors.ts";
import { Output } from "../../../../shared/output/output.service.ts";
import { Tty } from "../../../../shared/runtime/tty.service.ts";
import type { LegacyGenSigningKeyFlags } from "./signing-key.command.ts";
Expand Down Expand Up @@ -316,7 +317,7 @@ export const legacyGenSigningKey = Effect.fn("legacy.gen.signing-key")(function*
);
if (!confirmed) {
return yield* Effect.fail(
new LegacyGenSigningKeyCancelledError({ message: "context canceled" }),
new LegacyGenSigningKeyCancelledError({ message: CONTEXT_CANCELED_MESSAGE }),
);
}
return [key];
Expand Down
25 changes: 24 additions & 1 deletion apps/cli/src/legacy/commands/logout/logout.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { join } from "node:path";

import { describe, expect, test } from "vitest";

import { makeTempHome, runSupabase } from "../../../../tests/helpers/cli.ts";
import { makeTempHome, runSupabase, stripAnsi } from "../../../../tests/helpers/cli.ts";

const E2E_TIMEOUT_MS = 30_000;
const VALID_TOKEN = "sbp_" + "a".repeat(40);
Expand Down Expand Up @@ -37,6 +37,29 @@ describe("supabase logout (legacy)", () => {
},
);

// Declining the confirmation must byte-match Go: a single `context canceled`
// line on stderr and exit 1, with NO `--debug` troubleshooting hint —
// `recoverAndExit` skips `SuggestDebugFlag` for `context.Canceled`
// (apps/cli-go/cmd/root.go:287-303). CLI-1973.
test(
"declining the logout prompt prints only context canceled, no --debug hint",
{ timeout: E2E_TIMEOUT_MS },
async () => {
using home = makeTempHome();
seedTokenFile(home.dir);
const { exitCode, stderr } = await runSupabase(["logout"], {
entrypoint: "legacy",
home: home.dir,
env: { HOME: home.dir },
stdin: "n\n",
});
expect(exitCode).toBe(1);
const lines = stripAnsi(stderr).trimEnd().split("\n");
expect(lines.at(-1)).toBe("context canceled");
expect(stderr).not.toContain("Try rerunning the command with --debug");
},
);

// No token at all: same not-logged-in message, exit 0.
test(
"logout --yes with no token reports not-logged-in and exits 0",
Expand Down
11 changes: 6 additions & 5 deletions apps/cli/src/legacy/commands/logout/logout.errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { Data } from "effect";

/**
* Raised when the user declines the logout confirmation prompt. Go returns
* `errors.New(context.Canceled)` (`apps/cli-go/internal/logout/logout.go:18`),
* `errors.New(context.Canceled)` (`apps/cli-go/internal/logout/logout.go:19`),
* which the root error handler renders as `context canceled` on stderr with
* exit code 1 (`cmd/root.go:288-301` skips the debug suggestion for
* `context.Canceled`).
* exit code 1 and no `--debug` suggestion (`cmd/root.go:287-303` skips
* `SuggestDebugFlag` for `context.Canceled`). The TS renderer mirrors that:
* constructing this error with `CONTEXT_CANCELED_MESSAGE`
* (`shared/output/errors.ts`) is what makes the text `Output.fail` withhold
* the debug hint (CLI-1973).
*/
export class LegacyLogoutCancelledError extends Data.TaggedError("LegacyLogoutCancelledError")<{
readonly message: string;
}> {}

export const LEGACY_LOGOUT_CANCELLED_MESSAGE = "context canceled";
5 changes: 3 additions & 2 deletions apps/cli/src/legacy/commands/logout/logout.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { Effect } from "effect";
import { LegacyCredentials } from "../../auth/legacy-credentials.service.ts";
import { LegacyTelemetryState } from "../../telemetry/legacy-telemetry-state.service.ts";
import { legacyResolveYes } from "../../../shared/legacy/global-flags.ts";
import { CONTEXT_CANCELED_MESSAGE } from "../../../shared/output/errors.ts";
import { Output } from "../../../shared/output/output.service.ts";
import { legacyPromptYesNo } from "../../shared/legacy-prompt-yes-no.ts";
import { LegacyLogoutCancelledError, LEGACY_LOGOUT_CANCELLED_MESSAGE } from "./logout.errors.ts";
import { LegacyLogoutCancelledError } from "./logout.errors.ts";

const LOGGED_OUT_MSG = "Access token deleted successfully. You are now logged out.";

Expand Down Expand Up @@ -37,7 +38,7 @@ export const legacyLogout = Effect.fn("legacy.logout")(function* () {
});
if (!confirmed) {
return yield* Effect.fail(
new LegacyLogoutCancelledError({ message: LEGACY_LOGOUT_CANCELLED_MESSAGE }),
new LegacyLogoutCancelledError({ message: CONTEXT_CANCELED_MESSAGE }),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
legacyResolveYesWithProjectEnv,
} from "../../../../shared/legacy/global-flags.ts";
import { CliArgs } from "../../../../shared/cli/cli-args.service.ts";
import { CONTEXT_CANCELED_MESSAGE } from "../../../../shared/output/errors.ts";
import { Output } from "../../../../shared/output/output.service.ts";
import { LegacyCliConfig } from "../../../config/legacy-cli-config.service.ts";
import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts";
Expand Down Expand Up @@ -138,7 +139,7 @@ const runDown = Effect.fnUntraced(function* (
);
if (!confirmed) {
return yield* Effect.fail(
new LegacyOperationCanceledError({ message: "context canceled" }),
new LegacyOperationCanceledError({ message: CONTEXT_CANCELED_MESSAGE }),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { BunServices } from "@effect/platform-bun";
import { describe, expect, it } from "@effect/vitest";
import { Cause, Effect, Exit, Layer, Option } from "effect";

import { stripAnsi } from "../../../../../tests/helpers/ansi.ts";
import {
LEGACY_VALID_REF,
mockLegacyCliConfig,
Expand Down Expand Up @@ -151,8 +152,6 @@ const flags = (over: Partial<LegacyMigrationDownFlags> = {}): LegacyMigrationDow
local: over.local ?? true,
});

// eslint-disable-next-line no-control-regex
const stripAnsi = (text: string) => text.replace(/\x1b\[[0-9;]*m/gu, "");
const seed = (workdir: string, name: string, body = "create table a;\n") => {
const dir = join(workdir, "supabase", "migrations");
mkdirSync(dir, { recursive: true });
Expand Down
Loading
Loading