From 1539ba341ff4c476ad7999bf4b236f1e0ce6455c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 18 May 2026 14:38:25 +0000 Subject: [PATCH 1/2] feat(cli): add hidden flag support and Go parity hidden flags Introduces a withHidden() helper that registers a flag's name and filters it out of help-doc rendering, mirroring Cobra's MarkHidden which Effect's CLI does not yet expose. Help output for both text and JSON formatters now strips hidden flags. Adds the remaining Go-hidden flags to the legacy shell so the TS shim matches the Go CLI surface exactly: - start --preview - projects create --interactive (-i), --plan - functions deploy --use-docker, --legacy-bundle - functions download --use-docker, --legacy-bundle - functions serve --all Each flag is forwarded to the Go binary via the proxy handler when set. https://claude.ai/code/session_01KEg7JPF7EM7gicUD8KVpaa --- .../commands/functions/deploy/SIDE_EFFECTS.md | 1 + .../functions/deploy/deploy.command.ts | 11 ++++ .../functions/deploy/deploy.handler.ts | 4 ++ .../functions/download/SIDE_EFFECTS.md | 1 + .../functions/download/download.command.ts | 9 ++++ .../functions/download/download.handler.ts | 2 + .../commands/functions/serve/SIDE_EFFECTS.md | 1 + .../commands/functions/serve/serve.command.ts | 4 ++ .../commands/functions/serve/serve.handler.ts | 1 + .../projects/create/create.command.ts | 14 +++++ .../projects/create/create.handler.ts | 3 ++ .../legacy/commands/start/start.command.ts | 4 ++ .../legacy/commands/start/start.handler.ts | 1 + apps/cli/src/shared/cli/hidden-flag.ts | 44 ++++++++++++++++ .../src/shared/cli/hidden-flag.unit.test.ts | 52 +++++++++++++++++++ apps/cli/src/shared/output/json-formatter.ts | 4 +- apps/cli/src/shared/output/text-formatter.ts | 2 + 17 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 apps/cli/src/shared/cli/hidden-flag.ts create mode 100644 apps/cli/src/shared/cli/hidden-flag.unit.test.ts diff --git a/apps/cli/src/legacy/commands/functions/deploy/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/functions/deploy/SIDE_EFFECTS.md index 0dcd9848b..5e365210f 100644 --- a/apps/cli/src/legacy/commands/functions/deploy/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/functions/deploy/SIDE_EFFECTS.md @@ -59,4 +59,5 @@ Not applicable (proxied to Go binary). - Uses Docker by default to bundle functions; `--use-api` switches to server-side bundling. - `--prune` deletes functions that exist in the Supabase project but not locally. - `--jobs` (`-j`) sets the maximum number of parallel deploys; must be combined with `--use-api`. +- `--use-docker` and `--legacy-bundle` are hidden flags forwarded to the Go binary for backward compatibility; they are mutually exclusive with `--use-api`. - Phase 0 proxy: all invocations are forwarded to the bundled Go binary. diff --git a/apps/cli/src/legacy/commands/functions/deploy/deploy.command.ts b/apps/cli/src/legacy/commands/functions/deploy/deploy.command.ts index 8ef5ed4a1..502a45ab1 100644 --- a/apps/cli/src/legacy/commands/functions/deploy/deploy.command.ts +++ b/apps/cli/src/legacy/commands/functions/deploy/deploy.command.ts @@ -1,4 +1,5 @@ import { Argument, Command, Flag } from "effect/unstable/cli"; +import { withHidden } from "../../../../shared/cli/hidden-flag.ts"; import { legacyFunctionsDeploy } from "./deploy.handler.ts"; const config = { @@ -28,6 +29,14 @@ const config = { Flag.withDescription("Maximum number of parallel jobs."), Flag.optional, ), + useDocker: withHidden( + Flag.boolean("use-docker").pipe( + Flag.withDescription("Use Docker to bundle functions locally."), + ), + ), + legacyBundle: withHidden( + Flag.boolean("legacy-bundle").pipe(Flag.withDescription("Use legacy bundling.")), + ), } as const; export const legacyFunctionsDeployCommand = Command.make("deploy", config).pipe( @@ -42,6 +51,8 @@ export const legacyFunctionsDeployCommand = Command.make("deploy", config).pipe( importMap: flags.importMap, prune: flags.prune, jobs: flags.jobs, + useDocker: flags.useDocker, + legacyBundle: flags.legacyBundle, }), ), ); diff --git a/apps/cli/src/legacy/commands/functions/deploy/deploy.handler.ts b/apps/cli/src/legacy/commands/functions/deploy/deploy.handler.ts index b41d8313f..d84490cf5 100644 --- a/apps/cli/src/legacy/commands/functions/deploy/deploy.handler.ts +++ b/apps/cli/src/legacy/commands/functions/deploy/deploy.handler.ts @@ -9,6 +9,8 @@ interface LegacyFunctionsDeployFlags { readonly importMap: Option.Option; readonly prune: boolean; readonly jobs: Option.Option; + readonly useDocker: boolean; + readonly legacyBundle: boolean; } export const legacyFunctionsDeploy = Effect.fn("legacy.functions.deploy")(function* ( @@ -23,5 +25,7 @@ export const legacyFunctionsDeploy = Effect.fn("legacy.functions.deploy")(functi if (Option.isSome(flags.importMap)) args.push("--import-map", flags.importMap.value); if (flags.prune) args.push("--prune"); if (Option.isSome(flags.jobs)) args.push("--jobs", String(flags.jobs.value)); + if (flags.useDocker) args.push("--use-docker"); + if (flags.legacyBundle) args.push("--legacy-bundle"); yield* proxy.exec(args); }); diff --git a/apps/cli/src/legacy/commands/functions/download/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/functions/download/SIDE_EFFECTS.md index 8b9edea76..bcdc67265 100644 --- a/apps/cli/src/legacy/commands/functions/download/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/functions/download/SIDE_EFFECTS.md @@ -52,4 +52,5 @@ Not applicable (proxied to Go binary). - If no function name is provided, downloads all functions. - Requires a linked project (`--project-ref` or linked project config). +- `--use-docker` and `--legacy-bundle` are hidden flags forwarded to the Go binary for backward compatibility; they are mutually exclusive with `--use-api`. - Phase 0 proxy: all invocations are forwarded to the bundled Go binary. diff --git a/apps/cli/src/legacy/commands/functions/download/download.command.ts b/apps/cli/src/legacy/commands/functions/download/download.command.ts index ca6064cd7..c58288647 100644 --- a/apps/cli/src/legacy/commands/functions/download/download.command.ts +++ b/apps/cli/src/legacy/commands/functions/download/download.command.ts @@ -1,5 +1,6 @@ import { Argument, Command, Flag } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; +import { withHidden } from "../../../../shared/cli/hidden-flag.ts"; import { legacyFunctionsDownload } from "./download.handler.ts"; const config = { @@ -14,6 +15,14 @@ const config = { useApi: Flag.boolean("use-api").pipe( Flag.withDescription("Unbundle functions server-side without using Docker."), ), + useDocker: withHidden( + Flag.boolean("use-docker").pipe( + Flag.withDescription("Use Docker to unbundle functions locally."), + ), + ), + legacyBundle: withHidden( + Flag.boolean("legacy-bundle").pipe(Flag.withDescription("Use legacy bundling.")), + ), } as const; export type LegacyFunctionsDownloadFlags = CliCommand.Command.Config.Infer; diff --git a/apps/cli/src/legacy/commands/functions/download/download.handler.ts b/apps/cli/src/legacy/commands/functions/download/download.handler.ts index ca066b100..61bd8b4d4 100644 --- a/apps/cli/src/legacy/commands/functions/download/download.handler.ts +++ b/apps/cli/src/legacy/commands/functions/download/download.handler.ts @@ -10,5 +10,7 @@ export const legacyFunctionsDownload = Effect.fn("legacy.functions.download")(fu if (Option.isSome(flags.functionName)) args.push(flags.functionName.value); if (Option.isSome(flags.projectRef)) args.push("--project-ref", flags.projectRef.value); if (flags.useApi) args.push("--use-api"); + if (flags.useDocker) args.push("--use-docker"); + if (flags.legacyBundle) args.push("--legacy-bundle"); yield* proxy.exec(args); }); diff --git a/apps/cli/src/legacy/commands/functions/serve/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/functions/serve/SIDE_EFFECTS.md index dd3b88b9a..89d18e0ee 100644 --- a/apps/cli/src/legacy/commands/functions/serve/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/functions/serve/SIDE_EFFECTS.md @@ -55,4 +55,5 @@ Not applicable (proxied to Go binary). - `--env-file` path to env file populated to Function environment. - `--import-map` path to custom import map. - `--inspect` / `--inspect-mode` activates Deno inspector for debugging. +- `--all` is a hidden flag (default true) retained for backward compatibility; it has no effect because the Go CLI always serves all functions. - Phase 0 proxy: all invocations are forwarded to the bundled Go binary. diff --git a/apps/cli/src/legacy/commands/functions/serve/serve.command.ts b/apps/cli/src/legacy/commands/functions/serve/serve.command.ts index 7021d8410..d19ed641d 100644 --- a/apps/cli/src/legacy/commands/functions/serve/serve.command.ts +++ b/apps/cli/src/legacy/commands/functions/serve/serve.command.ts @@ -1,5 +1,6 @@ import { Command, Flag } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; +import { withHidden } from "../../../../shared/cli/hidden-flag.ts"; import { legacyFunctionsServe } from "./serve.handler.ts"; const INSPECT_MODES = ["run", "brk", "wait"] as const; @@ -24,6 +25,9 @@ const config = { inspectMain: Flag.boolean("inspect-main").pipe( Flag.withDescription("Allow inspecting the main worker."), ), + all: withHidden( + Flag.boolean("all").pipe(Flag.withDescription("Serve all Functions."), Flag.optional), + ), } as const; export type LegacyFunctionsServeFlags = CliCommand.Command.Config.Infer; diff --git a/apps/cli/src/legacy/commands/functions/serve/serve.handler.ts b/apps/cli/src/legacy/commands/functions/serve/serve.handler.ts index 5eaeb6f17..724b49edb 100644 --- a/apps/cli/src/legacy/commands/functions/serve/serve.handler.ts +++ b/apps/cli/src/legacy/commands/functions/serve/serve.handler.ts @@ -13,5 +13,6 @@ export const legacyFunctionsServe = Effect.fn("legacy.functions.serve")(function if (flags.inspect) args.push("--inspect"); if (Option.isSome(flags.inspectMode)) args.push("--inspect-mode", flags.inspectMode.value); if (flags.inspectMain) args.push("--inspect-main"); + if (Option.isSome(flags.all)) args.push(`--all=${flags.all.value ? "true" : "false"}`); yield* proxy.exec(args); }); diff --git a/apps/cli/src/legacy/commands/projects/create/create.command.ts b/apps/cli/src/legacy/commands/projects/create/create.command.ts index 942a3d305..b880639bb 100644 --- a/apps/cli/src/legacy/commands/projects/create/create.command.ts +++ b/apps/cli/src/legacy/commands/projects/create/create.command.ts @@ -1,5 +1,6 @@ import { Argument, Command, Flag } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; +import { withHidden } from "../../../../shared/cli/hidden-flag.ts"; import { legacyProjectsCreate } from "./create.handler.ts"; const AWS_REGIONS = [ @@ -66,6 +67,19 @@ const config = { Flag.withDescription("Select a desired instance size for your project."), Flag.optional, ), + interactive: withHidden( + Flag.boolean("interactive").pipe( + Flag.withDescription("Enables interactive mode."), + Flag.withAlias("i"), + Flag.optional, + ), + ), + plan: withHidden( + Flag.string("plan").pipe( + Flag.withDescription("Select a plan that suits your needs."), + Flag.optional, + ), + ), }; export type LegacyProjectsCreateFlags = CliCommand.Command.Config.Infer; diff --git a/apps/cli/src/legacy/commands/projects/create/create.handler.ts b/apps/cli/src/legacy/commands/projects/create/create.handler.ts index f9a8919c6..8c1f0060f 100644 --- a/apps/cli/src/legacy/commands/projects/create/create.handler.ts +++ b/apps/cli/src/legacy/commands/projects/create/create.handler.ts @@ -12,5 +12,8 @@ export const legacyProjectsCreate = Effect.fn("legacy.projects.create")(function if (Option.isSome(flags.dbPassword)) args.push("--db-password", flags.dbPassword.value); if (Option.isSome(flags.region)) args.push("--region", flags.region.value); if (Option.isSome(flags.size)) args.push("--size", flags.size.value); + if (Option.isSome(flags.interactive)) + args.push(`--interactive=${flags.interactive.value ? "true" : "false"}`); + if (Option.isSome(flags.plan)) args.push("--plan", flags.plan.value); yield* proxy.exec(args); }); diff --git a/apps/cli/src/legacy/commands/start/start.command.ts b/apps/cli/src/legacy/commands/start/start.command.ts index aa911a26c..7de8cae1b 100644 --- a/apps/cli/src/legacy/commands/start/start.command.ts +++ b/apps/cli/src/legacy/commands/start/start.command.ts @@ -1,5 +1,6 @@ import { Command, Flag } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; +import { withHidden } from "../../../shared/cli/hidden-flag.ts"; import { legacyStart } from "./start.handler.ts"; const config = { @@ -14,6 +15,9 @@ const config = { ignoreHealthCheck: Flag.boolean("ignore-health-check").pipe( Flag.withDescription("Ignore unhealthy services and exit 0"), ), + preview: withHidden( + Flag.boolean("preview").pipe(Flag.withDescription("Connect to feature preview branch")), + ), } as const; export type LegacyStartFlags = CliCommand.Command.Config.Infer; diff --git a/apps/cli/src/legacy/commands/start/start.handler.ts b/apps/cli/src/legacy/commands/start/start.handler.ts index e7768a997..2a77ea34f 100644 --- a/apps/cli/src/legacy/commands/start/start.handler.ts +++ b/apps/cli/src/legacy/commands/start/start.handler.ts @@ -7,5 +7,6 @@ export const legacyStart = Effect.fn("legacy.start")(function* (flags: LegacySta const args: string[] = ["start"]; for (const name of flags.exclude) args.push("--exclude", name); if (flags.ignoreHealthCheck) args.push("--ignore-health-check"); + if (flags.preview) args.push("--preview"); yield* proxy.exec(args); }); diff --git a/apps/cli/src/shared/cli/hidden-flag.ts b/apps/cli/src/shared/cli/hidden-flag.ts new file mode 100644 index 000000000..8cc4c0a0c --- /dev/null +++ b/apps/cli/src/shared/cli/hidden-flag.ts @@ -0,0 +1,44 @@ +import type { Flag, HelpDoc } from "effect/unstable/cli"; +import type * as Param from "effect/unstable/cli/Param"; + +const hiddenFlagNames = new Set(); + +const collectSingleNames = (param: Param.Param): Array => { + const node = param as + | Param.Single + | Param.Map + | Param.Transform + | Param.Optional + | Param.Variadic; + switch (node._tag) { + case "Single": + return [node.name]; + case "Map": + case "Transform": + case "Optional": + case "Variadic": + return collectSingleNames(node.param); + } +}; + +/** + * Marks a flag as hidden so that it is parsed normally but omitted from + * `--help` output. This mirrors Cobra's `MarkHidden` from the Go CLI, which + * the upstream Effect CLI does not yet expose natively. + */ +export const withHidden = (flag: Flag.Flag): Flag.Flag => { + for (const name of collectSingleNames(flag)) { + hiddenFlagNames.add(name); + } + return flag; +}; + +export const stripHiddenFlagsFromHelpDoc = (doc: HelpDoc.HelpDoc): HelpDoc.HelpDoc => { + const filteredFlags = doc.flags.filter((flag) => !hiddenFlagNames.has(flag.name)); + const filteredGlobalFlags = doc.globalFlags?.filter((flag) => !hiddenFlagNames.has(flag.name)); + return { + ...doc, + flags: filteredFlags, + ...(filteredGlobalFlags !== undefined && { globalFlags: filteredGlobalFlags }), + }; +}; diff --git a/apps/cli/src/shared/cli/hidden-flag.unit.test.ts b/apps/cli/src/shared/cli/hidden-flag.unit.test.ts new file mode 100644 index 000000000..43215e5e1 --- /dev/null +++ b/apps/cli/src/shared/cli/hidden-flag.unit.test.ts @@ -0,0 +1,52 @@ +import { Context, Option } from "effect"; +import { Flag, type HelpDoc } from "effect/unstable/cli"; +import { describe, expect, it } from "vitest"; +import { stripHiddenFlagsFromHelpDoc, withHidden } from "./hidden-flag.ts"; + +const flagDoc = (name: string): HelpDoc.FlagDoc => ({ + name, + aliases: [`--${name}`], + type: "boolean", + description: Option.none(), + required: false, +}); + +const helpDoc = (overrides: Partial): HelpDoc.HelpDoc => ({ + description: "", + usage: "", + flags: [], + annotations: Context.empty(), + ...overrides, +}); + +describe("withHidden", () => { + it("returns the same flag instance", () => { + const flag = Flag.boolean("legacy-bundle"); + expect(withHidden(flag)).toBe(flag); + }); + + it("registers the underlying single name even when wrapped with combinators", () => { + const flag = Flag.string("plan").pipe(Flag.optional); + withHidden(flag); + + const stripped = stripHiddenFlagsFromHelpDoc( + helpDoc({ flags: [flagDoc("plan"), flagDoc("visible")] }), + ); + expect(stripped.flags.map((f) => f.name)).toEqual(["visible"]); + }); + + it("filters hidden flags from globalFlags as well", () => { + withHidden(Flag.boolean("preview")); + + const stripped = stripHiddenFlagsFromHelpDoc( + helpDoc({ globalFlags: [flagDoc("preview"), flagDoc("verbose")] }), + ); + expect(stripped.globalFlags?.map((f) => f.name)).toEqual(["verbose"]); + }); + + it("leaves docs without globalFlags untouched", () => { + const stripped = stripHiddenFlagsFromHelpDoc(helpDoc({ flags: [flagDoc("foo")] })); + expect(stripped.globalFlags).toBeUndefined(); + expect(stripped.flags.map((f) => f.name)).toEqual(["foo"]); + }); +}); diff --git a/apps/cli/src/shared/output/json-formatter.ts b/apps/cli/src/shared/output/json-formatter.ts index e5fb754aa..494739913 100644 --- a/apps/cli/src/shared/output/json-formatter.ts +++ b/apps/cli/src/shared/output/json-formatter.ts @@ -1,8 +1,10 @@ import type { CliOutput, HelpDoc } from "effect/unstable/cli"; +import { stripHiddenFlagsFromHelpDoc } from "../cli/hidden-flag.ts"; export function jsonCliOutputFormatter(): CliOutput.Formatter { return { - formatHelpDoc: (doc: HelpDoc.HelpDoc) => JSON.stringify({ _tag: "Help", doc }), + formatHelpDoc: (doc: HelpDoc.HelpDoc) => + JSON.stringify({ _tag: "Help", doc: stripHiddenFlagsFromHelpDoc(doc) }), formatCliError: (error) => JSON.stringify({ _tag: "Error", error: { code: error._tag, message: error.message } }), formatError: (error) => diff --git a/apps/cli/src/shared/output/text-formatter.ts b/apps/cli/src/shared/output/text-formatter.ts index 4b497742e..706e7d28a 100644 --- a/apps/cli/src/shared/output/text-formatter.ts +++ b/apps/cli/src/shared/output/text-formatter.ts @@ -1,9 +1,11 @@ import { CliOutput } from "effect/unstable/cli"; +import { stripHiddenFlagsFromHelpDoc } from "../cli/hidden-flag.ts"; export function textCliOutputFormatter(): CliOutput.Formatter { const base = CliOutput.defaultFormatter({ colors: false }); return { ...base, + formatHelpDoc: (doc) => base.formatHelpDoc(stripHiddenFlagsFromHelpDoc(doc)), formatVersion: (_name, version) => version, }; } From 0c1791b01afddc9a88e3f5cd1c9a2d327c258ddf Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 18 May 2026 14:44:09 +0000 Subject: [PATCH 2/2] feat(cli): accept --linked/--local on legacy seed buckets The Go CLI defines --linked and --local as persistent flags on the seed group, so seed buckets accepts them (as a no-op, since buckets hardcodes linked=true). Effect CLI does not expose group-level persistent flags, so the TS shim previously rejected them. Adds them as explicit flags on the buckets subcommand and forwards to the Go binary to preserve invocation parity. https://claude.ai/code/session_01KEg7JPF7EM7gicUD8KVpaa --- .../src/legacy/commands/seed/buckets/buckets.command.ts | 7 +++++-- .../src/legacy/commands/seed/buckets/buckets.handler.ts | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/cli/src/legacy/commands/seed/buckets/buckets.command.ts b/apps/cli/src/legacy/commands/seed/buckets/buckets.command.ts index ba029162e..ee781406b 100644 --- a/apps/cli/src/legacy/commands/seed/buckets/buckets.command.ts +++ b/apps/cli/src/legacy/commands/seed/buckets/buckets.command.ts @@ -1,8 +1,11 @@ -import { Command } from "effect/unstable/cli"; +import { Command, Flag } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; import { legacyBuckets } from "./buckets.handler.ts"; -const config = {} as const; +const config = { + linked: Flag.boolean("linked").pipe(Flag.withDescription("Seeds the linked project.")), + local: Flag.boolean("local").pipe(Flag.withDescription("Seeds the local database.")), +} as const; export type LegacyBucketsFlags = CliCommand.Command.Config.Infer; diff --git a/apps/cli/src/legacy/commands/seed/buckets/buckets.handler.ts b/apps/cli/src/legacy/commands/seed/buckets/buckets.handler.ts index 67c824ef2..7af772419 100644 --- a/apps/cli/src/legacy/commands/seed/buckets/buckets.handler.ts +++ b/apps/cli/src/legacy/commands/seed/buckets/buckets.handler.ts @@ -3,8 +3,11 @@ import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; import type { LegacyBucketsFlags } from "./buckets.command.ts"; export const legacyBuckets = Effect.fn("legacy.seed.buckets")(function* ( - _flags: LegacyBucketsFlags, + flags: LegacyBucketsFlags, ) { const proxy = yield* LegacyGoProxy; - yield* proxy.exec(["seed", "buckets"]); + const args: string[] = ["seed", "buckets"]; + if (flags.linked) args.push("--linked"); + if (flags.local) args.push("--local"); + yield* proxy.exec(args); });