Skip to content
Open
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
14 changes: 7 additions & 7 deletions apps/cli/docs/go-cli-porting-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,13 @@ These route-first equivalents are intentionally lower-level than the old Go comm

## Additional Commands

| Old command | TS status | TS command path or `missing` | Missing flags/params | Extra TS flags/params | Notes |
| ----------------------- | --------- | -------------------------------- | --------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------ |
| `completion bash` | `ported` | `supabase completion bash` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). |
| `completion fish` | `ported` | `supabase completion fish` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). |
| `completion powershell` | `ported` | `supabase completion powershell` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). |
| `completion zsh` | `ported` | `supabase completion zsh` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). |
| `help` | `partial` | `supabase --help` | Go-style top-level `help` command shape | `-` | Feature parity exists via the framework-provided global `--help` flag instead of a dedicated `help` command. |
| Old command | TS status | TS command path or `missing` | Missing flags/params | Extra TS flags/params | Notes |
| ----------------------- | --------- | -------------------------------- | --------------------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `completion bash` | `ported` | `supabase completion bash` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). `--no-descriptions` added to match cobra's auto-registered flag (CLI-1858). |
| `completion fish` | `ported` | `supabase completion fish` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). `--no-descriptions` added to match cobra's auto-registered flag (CLI-1858). |
| `completion powershell` | `ported` | `supabase completion powershell` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). `--no-descriptions` added to match cobra's auto-registered flag (CLI-1858). |
| `completion zsh` | `ported` | `supabase completion zsh` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). `--no-descriptions` added to match cobra's auto-registered flag (CLI-1858). |
| `help` | `partial` | `supabase --help` | Go-style top-level `help` command shape | `-` | Feature parity exists via the framework-provided global `--help` flag instead of a dedicated `help` command. |

## Legacy Shell Wrapping Status

Expand Down
12 changes: 8 additions & 4 deletions apps/cli/src/legacy/cli/complete-passthrough.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@ export interface CompletePassthroughDeps {

/**
* Cobra-generated completion scripts (`supabase completion {bash,zsh,fish,powershell}`)
* call back into `supabase __complete <args>` on every tab press. The args may
* include partial flag tokens (e.g. `--de` while the user is mid-completion of a
* flag name) that Effect's structured parser would reject. Bypass Effect entirely
* call back into `supabase __complete <args>` on every tab press — or
* `supabase __completeNoDesc <args>` when the script was generated with
* `--no-descriptions` (`__completeNoDesc` is cobra's alias for the same hidden
* command, `ShellCompNoDescRequestCmd` in `spf13/cobra@v1.10.2/completions.go`,
* baked into the generated script at generation time). The args may include
* partial flag tokens (e.g. `--de` while the user is mid-completion of a flag
* name) that Effect's structured parser would reject. Bypass Effect entirely
* for this code path and proxy the raw argv to the bundled Go binary, which is
* the authority on completion behavior for the legacy shell.
*
* Returns `true` when the call was intercepted (caller must not continue), `false`
* otherwise.
*/
export function tryCompletePassthrough(deps: CompletePassthroughDeps): boolean {
if (deps.argv[0] !== "__complete") return false;
if (deps.argv[0] !== "__complete" && deps.argv[0] !== "__completeNoDesc") return false;

const resolved = deps.resolveBinary();
if (!("found" in resolved)) {
Expand Down
11 changes: 11 additions & 0 deletions apps/cli/src/legacy/cli/complete-passthrough.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ describe("tryCompletePassthrough", () => {
expect(exits).toEqual([0]);
});

it("forwards verbatim argv to the Go binary on __completeNoDesc (scripts generated with --no-descriptions)", () => {
const { deps, spawnCalls, exits } = makeDeps({
argv: ["__completeNoDesc", "migration", "li"],
});
expect(tryCompletePassthrough(deps)).toBe(true);
expect(spawnCalls).toEqual([
{ cmd: "/path/to/supabase-go", args: ["__completeNoDesc", "migration", "li"] },
]);
expect(exits).toEqual([0]);
});

it("propagates the child's non-zero exit code", () => {
const spawn = vi.fn(() => spawnResult(7));
const { deps, exits } = makeDeps({ spawn });
Expand Down
11 changes: 9 additions & 2 deletions apps/cli/src/legacy/commands/completion/SIDE_EFFECTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ brew-managed `_supabase` files in their `fpath`, or analogous bash/fish/powershe
artifacts. Drift would break tab completion for those users.

The generated scripts call back to `supabase __complete <args>` on every tab press to
fetch dynamic completion candidates — see `apps/cli/src/legacy/commands/__complete/`,
which provides the matching hidden command.
fetch dynamic completion candidates, or `supabase __completeNoDesc <args>` when the
script was generated with `--no-descriptions` (cobra's alias for the same hidden
command) — see `apps/cli/src/legacy/cli/complete-passthrough.ts`, which intercepts
both `__complete` and `__completeNoDesc` before Effect's argv parser and proxies them
straight to the Go binary.

## Notes

Expand All @@ -58,3 +61,7 @@ which provides the matching hidden command.
- The Go CLI exits non-zero when called without a shell subcommand (e.g.
`supabase completion`). Effect CLI surfaces the same condition through its usual
"missing subcommand" help-with-exit-1 behavior.
- Each of `bash`/`zsh`/`fish`/`powershell` declares `--no-descriptions` (cobra's
auto-registered flag, `completions.go` in `spf13/cobra`) and forwards it to the
Go binary, so the emitted script omits completion descriptions exactly as it
would with the Go CLI.
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Command } from "effect/unstable/cli";
import type * as CliCommand from "effect/unstable/cli/Command";
import { LegacyCompletionNoDescriptionsFlagDef } from "../completion.flags.ts";
import { legacyCompletionBash } from "./bash.handler.ts";

const config = {};
const config = {
noDescriptions: LegacyCompletionNoDescriptionsFlagDef,
} as const;
export type LegacyCompletionBashFlags = CliCommand.Command.Config.Infer<typeof config>;

export const legacyCompletionBashCommand = Command.make("bash", config).pipe(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts";
import type { LegacyCompletionBashFlags } from "./bash.command.ts";

export const legacyCompletionBash = Effect.fn("legacy.completion.bash")(function* (
_flags: LegacyCompletionBashFlags,
flags: LegacyCompletionBashFlags,
) {
const proxy = yield* LegacyGoProxy;
yield* proxy.exec(["completion", "bash"]);
const args: string[] = ["completion", "bash"];
if (flags.noDescriptions) args.push("--no-descriptions");
Comment thread
Coly010 marked this conversation as resolved.
yield* proxy.exec(args);
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { describe, expect, it } from "@effect/vitest";
import { Effect, Layer } from "effect";
import { Command } from "effect/unstable/cli";
import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts";
import { legacyCompletionBashCommand } from "./bash.command.ts";
import { legacyCompletionBash } from "./bash.handler.ts";

function setupLegacyCompletionBash() {
Expand All @@ -15,12 +17,35 @@ function setupLegacyCompletionBash() {
return { layer, calls };
}

function legacyTestRoot() {
return Command.make("supabase").pipe(Command.withSubcommands([legacyCompletionBashCommand]));
}

describe("legacy completion bash", () => {
it.live("forwards `completion bash` to the Go binary", () => {
const { layer, calls } = setupLegacyCompletionBash();
return Effect.gen(function* () {
yield* legacyCompletionBash({});
yield* legacyCompletionBash({ noDescriptions: false });
expect(calls).toEqual([["completion", "bash"]]);
}).pipe(Effect.provide(layer));
});

it.live("forwards --no-descriptions when set", () => {
const { layer, calls } = setupLegacyCompletionBash();
return Effect.gen(function* () {
yield* legacyCompletionBash({ noDescriptions: true });
expect(calls).toEqual([["completion", "bash", "--no-descriptions"]]);
}).pipe(Effect.provide(layer));
});

it.live("accepts --no-descriptions from real argv via the command parser", () => {
const { layer, calls } = setupLegacyCompletionBash();
return Effect.gen(function* () {
yield* Command.runWith(legacyTestRoot(), { version: "0.0.0-test" })([
"bash",
"--no-descriptions",
]);
expect(calls).toEqual([["completion", "bash", "--no-descriptions"]]);
}).pipe(Effect.provide(layer)) as Effect.Effect<void>;
});
});
25 changes: 25 additions & 0 deletions apps/cli/src/legacy/commands/completion/completion.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, expect, test } from "vitest";
import { runSupabase } from "../../../../tests/helpers/cli.ts";

const E2E_TIMEOUT_MS = 30_000;

describe("supabase completion (legacy)", () => {
// Golden-path e2e for CLI-1858: `--no-descriptions` used to be rejected by
// Effect's argv parser (`UnrecognizedOption`) before the request ever
// reached the Go binary, because the flag wasn't declared on the TS leaf
// command. Only a real subprocess run proves both halves of the fix: the
// TS parser accepts the flag, and the Go binary actually receives it — it
// switches the generated script's completion callback from `__complete` to
// `__completeNoDesc` only when the flag is forwarded.
test(
"bash --no-descriptions is accepted and forwarded to the Go binary",
{ timeout: E2E_TIMEOUT_MS },
async () => {
const { exitCode, stdout } = await runSupabase(["completion", "bash", "--no-descriptions"], {
entrypoint: "legacy",
});
expect(exitCode).toBe(0);
expect(stdout).toContain("__completeNoDesc");
},
);
});
17 changes: 17 additions & 0 deletions apps/cli/src/legacy/commands/completion/completion.flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Flag } from "effect/unstable/cli";

/**
* cobra auto-registers `--no-descriptions` on the bash/zsh/fish/powershell
* completion subcommands whenever descriptions are enabled (the default) —
* `compCmdNoDescFlagName`/`compCmdNoDescFlagDefault`/`compCmdNoDescFlagDesc`
* in `spf13/cobra@v1.10.2/completions.go:101-103`. Shared across all four
* leaves rather than redeclared per-file.
*
* `Flag.boolean` auto-derives a `--no-<name>` negation, so this flag name
* produces `--no-no-descriptions` as a working (if odd) way to re-enable
* descriptions. Harmless — it resolves to the same `false` default — and
* not something cobra does, so there's no parity requirement to remove it.
*/
export const LegacyCompletionNoDescriptionsFlagDef = Flag.boolean("no-descriptions").pipe(
Flag.withDescription("disable completion descriptions"),
);
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Command } from "effect/unstable/cli";
import type * as CliCommand from "effect/unstable/cli/Command";
import { LegacyCompletionNoDescriptionsFlagDef } from "../completion.flags.ts";
import { legacyCompletionFish } from "./fish.handler.ts";

const config = {};
const config = {
noDescriptions: LegacyCompletionNoDescriptionsFlagDef,
} as const;
export type LegacyCompletionFishFlags = CliCommand.Command.Config.Infer<typeof config>;

export const legacyCompletionFishCommand = Command.make("fish", config).pipe(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts";
import type { LegacyCompletionFishFlags } from "./fish.command.ts";

export const legacyCompletionFish = Effect.fn("legacy.completion.fish")(function* (
_flags: LegacyCompletionFishFlags,
flags: LegacyCompletionFishFlags,
) {
const proxy = yield* LegacyGoProxy;
yield* proxy.exec(["completion", "fish"]);
const args: string[] = ["completion", "fish"];
if (flags.noDescriptions) args.push("--no-descriptions");
yield* proxy.exec(args);
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { describe, expect, it } from "@effect/vitest";
import { Effect, Layer } from "effect";
import { Command } from "effect/unstable/cli";
import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts";
import { legacyCompletionFishCommand } from "./fish.command.ts";
import { legacyCompletionFish } from "./fish.handler.ts";

function setupLegacyCompletionFish() {
Expand All @@ -15,12 +17,35 @@ function setupLegacyCompletionFish() {
return { layer, calls };
}

function legacyTestRoot() {
return Command.make("supabase").pipe(Command.withSubcommands([legacyCompletionFishCommand]));
}

describe("legacy completion fish", () => {
it.live("forwards `completion fish` to the Go binary", () => {
const { layer, calls } = setupLegacyCompletionFish();
return Effect.gen(function* () {
yield* legacyCompletionFish({});
yield* legacyCompletionFish({ noDescriptions: false });
expect(calls).toEqual([["completion", "fish"]]);
}).pipe(Effect.provide(layer));
});

it.live("forwards --no-descriptions when set", () => {
const { layer, calls } = setupLegacyCompletionFish();
return Effect.gen(function* () {
yield* legacyCompletionFish({ noDescriptions: true });
expect(calls).toEqual([["completion", "fish", "--no-descriptions"]]);
}).pipe(Effect.provide(layer));
});

it.live("accepts --no-descriptions from real argv via the command parser", () => {
const { layer, calls } = setupLegacyCompletionFish();
return Effect.gen(function* () {
yield* Command.runWith(legacyTestRoot(), { version: "0.0.0-test" })([
"fish",
"--no-descriptions",
]);
expect(calls).toEqual([["completion", "fish", "--no-descriptions"]]);
}).pipe(Effect.provide(layer)) as Effect.Effect<void>;
});
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Command } from "effect/unstable/cli";
import type * as CliCommand from "effect/unstable/cli/Command";
import { LegacyCompletionNoDescriptionsFlagDef } from "../completion.flags.ts";
import { legacyCompletionPowershell } from "./powershell.handler.ts";

const config = {};
const config = {
noDescriptions: LegacyCompletionNoDescriptionsFlagDef,
} as const;
export type LegacyCompletionPowershellFlags = CliCommand.Command.Config.Infer<typeof config>;

export const legacyCompletionPowershellCommand = Command.make("powershell", config).pipe(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts";
import type { LegacyCompletionPowershellFlags } from "./powershell.command.ts";

export const legacyCompletionPowershell = Effect.fn("legacy.completion.powershell")(function* (
_flags: LegacyCompletionPowershellFlags,
flags: LegacyCompletionPowershellFlags,
) {
const proxy = yield* LegacyGoProxy;
yield* proxy.exec(["completion", "powershell"]);
const args: string[] = ["completion", "powershell"];
if (flags.noDescriptions) args.push("--no-descriptions");
yield* proxy.exec(args);
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { describe, expect, it } from "@effect/vitest";
import { Effect, Layer } from "effect";
import { Command } from "effect/unstable/cli";
import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts";
import { legacyCompletionPowershellCommand } from "./powershell.command.ts";
import { legacyCompletionPowershell } from "./powershell.handler.ts";

function setupLegacyCompletionPowershell() {
Expand All @@ -15,12 +17,37 @@ function setupLegacyCompletionPowershell() {
return { layer, calls };
}

function legacyTestRoot() {
return Command.make("supabase").pipe(
Command.withSubcommands([legacyCompletionPowershellCommand]),
);
}

describe("legacy completion powershell", () => {
it.live("forwards `completion powershell` to the Go binary", () => {
const { layer, calls } = setupLegacyCompletionPowershell();
return Effect.gen(function* () {
yield* legacyCompletionPowershell({});
yield* legacyCompletionPowershell({ noDescriptions: false });
expect(calls).toEqual([["completion", "powershell"]]);
}).pipe(Effect.provide(layer));
});

it.live("forwards --no-descriptions when set", () => {
const { layer, calls } = setupLegacyCompletionPowershell();
return Effect.gen(function* () {
yield* legacyCompletionPowershell({ noDescriptions: true });
expect(calls).toEqual([["completion", "powershell", "--no-descriptions"]]);
}).pipe(Effect.provide(layer));
});

it.live("accepts --no-descriptions from real argv via the command parser", () => {
const { layer, calls } = setupLegacyCompletionPowershell();
return Effect.gen(function* () {
yield* Command.runWith(legacyTestRoot(), { version: "0.0.0-test" })([
"powershell",
"--no-descriptions",
]);
expect(calls).toEqual([["completion", "powershell", "--no-descriptions"]]);
}).pipe(Effect.provide(layer)) as Effect.Effect<void>;
});
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Command } from "effect/unstable/cli";
import type * as CliCommand from "effect/unstable/cli/Command";
import { LegacyCompletionNoDescriptionsFlagDef } from "../completion.flags.ts";
import { legacyCompletionZsh } from "./zsh.handler.ts";

const config = {};
const config = {
noDescriptions: LegacyCompletionNoDescriptionsFlagDef,
} as const;
export type LegacyCompletionZshFlags = CliCommand.Command.Config.Infer<typeof config>;

export const legacyCompletionZshCommand = Command.make("zsh", config).pipe(
Expand Down
6 changes: 4 additions & 2 deletions apps/cli/src/legacy/commands/completion/zsh/zsh.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts";
import type { LegacyCompletionZshFlags } from "./zsh.command.ts";

export const legacyCompletionZsh = Effect.fn("legacy.completion.zsh")(function* (
_flags: LegacyCompletionZshFlags,
flags: LegacyCompletionZshFlags,
) {
const proxy = yield* LegacyGoProxy;
yield* proxy.exec(["completion", "zsh"]);
const args: string[] = ["completion", "zsh"];
if (flags.noDescriptions) args.push("--no-descriptions");
yield* proxy.exec(args);
});
Loading
Loading