Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c249464
feat(cli): add experimental stack start
jgoux Sep 7, 2026
c370b5a
fix(cli): validate experimental stack config listeners
jgoux Sep 7, 2026
ea97377
fix(cli): omit empty experimental stack listeners
jgoux Sep 7, 2026
2290b64
fix(cli): tighten experimental stack start config
jgoux Sep 8, 2026
02c669c
test(stack): stabilize ingress listener fixtures
jgoux Sep 8, 2026
99873ec
fix(cli): harden experimental stack config paths
jgoux Sep 8, 2026
e44ab9f
test(stack): stabilize ingress listener fixtures
jgoux Sep 8, 2026
9acb080
test(stack): stabilize remaining ingress ports
jgoux Sep 8, 2026
8c29ef7
fix(cli): scope stack config path service
jgoux Sep 8, 2026
cb78d99
fix(cli): clarify stack state recovery guidance
jgoux Sep 8, 2026
ef2ab0a
style(cli): format stack start handler
jgoux Sep 8, 2026
3055b18
test(cli): consume exported stack config errors
jgoux Sep 8, 2026
03ccaa6
chore(cli): extend experimental stack effect lint
jgoux Sep 8, 2026
bf1269f
fix(stack): explain signing key path restrictions
jgoux Sep 8, 2026
99d490c
fix(cli): retain the API listener for analytics
jgoux Sep 8, 2026
6a5fac6
feat(cli): add experimental stack stop
jgoux Sep 7, 2026
d0733f8
docs(cli): document experimental stack stop effects
jgoux Sep 7, 2026
08de239
fix(cli): refine experimental stack stop output
jgoux Sep 7, 2026
a2035be
fix(cli): classify invalid stack stop names
jgoux Sep 7, 2026
4b59856
test(cli): cover experimental stack stop parsing
jgoux Sep 7, 2026
b0a2677
test(cli): cover experimental stack stop errors
jgoux Sep 7, 2026
52f511d
chore(cli): annotate stack stop fixtures
jgoux Sep 8, 2026
63c9f23
test(cli): complete stack stop service mocks
jgoux Sep 8, 2026
c911669
feat(cli): expose stack discovery service
jgoux Sep 8, 2026
865c92f
test(cli): scope stop mock to available stack methods
jgoux Sep 8, 2026
d876873
feat(stack): report redacted configuration drift
jgoux Sep 7, 2026
9f2a37a
feat(cli): add experimental stack status
jgoux Sep 7, 2026
aacd97b
fix(cli): classify experimental stack status errors
jgoux Sep 7, 2026
a8de60a
fix(cli): refine experimental stack status reporting
jgoux Sep 7, 2026
551ef2d
test(cli): cover experimental stack status output parity
jgoux Sep 7, 2026
e460c13
fix(cli): clarify experimental stack status
jgoux Sep 7, 2026
4cb228f
fix(cli): clarify missing stack status guidance
jgoux Sep 7, 2026
67db1a8
chore(cli): annotate stack status fixtures
jgoux Sep 8, 2026
0a7ef7d
test(cli): complete stack status service mocks
jgoux Sep 8, 2026
34ca600
test(cli): scope status mock to available stack methods
jgoux Sep 8, 2026
a1946b6
feat(cli): add experimental stack list
jgoux Sep 7, 2026
7d36677
fix(cli): refine experimental stack list reporting
jgoux Sep 7, 2026
664e029
test(cli): fix list container fixture
jgoux Sep 7, 2026
77028b2
test(cli): strengthen stack list ordering assertion
jgoux Sep 7, 2026
88add78
test(cli): require both stack sort entries
jgoux Sep 7, 2026
f4553d7
fix(cli): address experimental stack list review
jgoux Sep 8, 2026
ca4f2b2
fix(stack): satisfy effect lint in stack discovery
jgoux Sep 8, 2026
edeccb4
test(cli): add list operation to stack service fixtures
jgoux Sep 8, 2026
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ These workspaces should generally follow this structure:
- Standard scripts: `test`, `types:check`
- Standard devDependencies: `@tsconfig/bun`, `@types/bun`, `typescript`

Generic linting (`oxlint`), formatting (`oxfmt`), and unused-code analysis (`knip`) are repo-wide, not per-package: the tools are root devDependencies configured by `.oxlintrc.json`, `.oxfmtrc.json`, and `knip.json` at the repo root (knip's config maps each workspace under its `workspaces` key). Effect-specific linting is scoped to `packages/stack` through `.oxlintrc.effect.json`; run it with the root `lint:effect:check` or `lint:effect:fix` scripts. The root `check:all`/`fix:all` scripts are the sole repo-wide quality entrypoints and use Turbo to orchestrate the root-owned generic `lint:*`/`fmt:*`/`knip:*` scripts and package `types:check` targets; `fix:all` runs the Effect lint fix after those generic fixes complete. Package-local work can run `pnpm types:check` and the package's test scripts; `pnpm exec oxlint`, `pnpm exec oxfmt`, and `pnpm exec knip-bun` from the repo root also work directly.
Generic linting (`oxlint`), formatting (`oxfmt`), and unused-code analysis (`knip`) are repo-wide, not per-package: the tools are root devDependencies configured by `.oxlintrc.json`, `.oxfmtrc.json`, and `knip.json` at the repo root (knip's config maps each workspace under its `workspaces` key). Effect-specific linting covers `packages/stack` and all files under `apps/cli/src/commands/experimental/stack` through `.oxlintrc.effect.json`; run it with the root `lint:effect:check` or `lint:effect:fix` scripts. The root `check:all`/`fix:all` scripts are the sole repo-wide quality entrypoints and use Turbo to orchestrate the root-owned generic `lint:*`/`fmt:*`/`knip:*` scripts and package `types:check` targets; `fix:all` runs the Effect lint fix after those generic fixes complete. Package-local work can run `pnpm types:check` and the package's test scripts; `pnpm exec oxlint`, `pnpm exec oxfmt`, and `pnpm exec knip-bun` from the repo root also work directly.

Expected exceptions:

Expand Down
1 change: 1 addition & 0 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@supabase/config": "workspace:*",
"@supabase/pg-delta": "1.0.0-alpha.49",
"@supabase/pg-topo": "1.0.0-alpha.6",
"@supabase/stack": "workspace:*",
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@types/pg": "^8.23.1",
Expand Down
4 changes: 4 additions & 0 deletions apps/cli/src/command-internal/legacy-db-target-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ export const VALUE_CONSUMING_LONG_FLAGS = new Set([
"link",
"issue-type",
"improvement",
// experimental stack start flags
"stack",
"stack-id",
"preparation",
]);

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Command } from "effect/unstable/cli";
import { legacyWorkersCommand } from "./workers/workers.command.ts";
import { legacyExperimentalStackCommand } from "./stack/stack.command.ts";

/**
* `supabase experimental` — the parent for command families that are not yet
Expand All @@ -17,6 +18,6 @@ export const legacyExperimentalCommand = Command.make("experimental").pipe(
"Experimental commands. These are unstable: their flags, output, and invocation path can change or be removed in any release, and they are excluded from the CLI's compatibility promise.",
),
Command.withShortDescription("Experimental, unstable commands"),
Command.withSubcommands([legacyWorkersCommand]),
Command.withSubcommands([legacyWorkersCommand, legacyExperimentalStackCommand]),
Command.unlisted,
);
16 changes: 16 additions & 0 deletions apps/cli/src/commands/experimental/stack/list/SIDE_EFFECTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# `supabase experimental stack list`

Lists persisted managed local stacks discovered in the global stack registry.
The command includes stopped stacks and performs no config loading, owner RPC,
activation, or lifecycle mutation. Registry discovery is currently fail-fast: a
corrupt or unsupported entry can prevent other entries from being listed.

Entries are sorted by project root, stack name, and id. Text output includes the
identity, project root, branch context, runtime, and desired lifecycle. Structured
output returns the same fields under `stacks`. The legacy `-o/--output` flag is
rejected; use `--output-format json`.

The command reads `${SUPABASE_HOME ?? $HOME/.supabase}/managed/stacks/<stack-id>/`,
including each persisted state document and state remnant metadata. It consumes
`SUPABASE_HOME`, falling back to `HOME/.supabase`. Exit code 0 indicates success;
exit code 1 indicates a registry read error or rejected legacy output flag.
12 changes: 12 additions & 0 deletions apps/cli/src/commands/experimental/stack/list/list.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Command } from "effect/unstable/cli";
import { withJsonErrorHandling } from "../../../../shared/output/json-error-handling.ts";
import { withLegacyCommandInstrumentation } from "../../../../telemetry/legacy-command-instrumentation.ts";
import { legacyExperimentalStackList } from "./list.handler.ts";

export const legacyExperimentalStackListCommand = Command.make("list").pipe(
Command.withDescription("List persisted managed local Supabase stacks."),
Command.withShortDescription("List managed local stacks"),
Command.withHandler(() =>
legacyExperimentalStackList().pipe(withLegacyCommandInstrumentation(), withJsonErrorHandling),
),
);
20 changes: 20 additions & 0 deletions apps/cli/src/commands/experimental/stack/list/list.errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Data } from "effect";
import {
actionability,
type CliErrorActionabilityDeclaration,
ErrorActionabilityId,
} from "../../../../shared/telemetry/error-actionability.ts";

export class LegacyExperimentalStackListError extends Data.TaggedError(
"LegacyExperimentalStackListError",
)<{
readonly message: string;
readonly reason: "flags" | "invalid-config";
readonly suggestion?: string;
readonly cause?: unknown;
}> {
get [ErrorActionabilityId](): CliErrorActionabilityDeclaration {
if (this.reason === "flags") return actionability.provideFlags;
return actionability.invalidConfig;
}
}
81 changes: 81 additions & 0 deletions apps/cli/src/commands/experimental/stack/list/list.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Effect, Match, Option } from "effect";
import {
type StackDescriptor,
type StackDiscoveryError,
type StackRuntime,
} from "@supabase/stack/effect";
import { Output } from "../../../../shared/output/output.service.ts";
import { LegacyOutputFlag } from "../../../../shared/legacy/global-flags.ts";
import { LegacyExperimentalStackApi } from "../stack.shared.ts";
import { LegacyExperimentalStackListError } from "./list.errors.ts";

const entry = (descriptor: StackDescriptor) => ({
id: descriptor.id,
project_root: descriptor.projectRoot,
name: descriptor.name,
branch_context: descriptor.branchContext,
runtime: descriptor.runtime,
desired_lifecycle: descriptor.desiredLifecycle,
});

const compareCodeunit = (left: string, right: string): number =>
left === right ? 0 : left < right ? -1 : 1;

const compareEntries = (
left: ReturnType<typeof entry>,
right: ReturnType<typeof entry>,
): number => {
const project = compareCodeunit(left.project_root, right.project_root);
if (project !== 0) return project;
const name = compareCodeunit(left.name, right.name);
return name !== 0 ? name : compareCodeunit(left.id, right.id);
};

const mapStackError = (error: StackDiscoveryError) =>
new LegacyExperimentalStackListError({
reason: "invalid-config",
message: error.message,
suggestion:
"Inspect the managed stack registry under $SUPABASE_HOME/managed/stacks or ~/.supabase/managed/stacks.",
cause: error,
});
Comment on lines +34 to +41

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MINOR · error-handling · source: claude

All stack-discovery failures are classified as invalid configuration, including registry filesystem and permission failures.

Evidence: apps/cli/src/commands/experimental/stack/list/list.handler.ts:34-41 always assigns reason "invalid-config". packages/stack/src/public/EffectStack.ts:1089-1097 converts failures from exists/readDirectory into StackStateInvalidError, so platform I/O failures reach that classification.

Suggested fix: Preserve enough platform-error information in the stack API to distinguish registry I/O failures, then give those failures a non-configuration actionability and targeted filesystem guidance.


const renderRuntime = (runtime: StackRuntime): string =>
Match.value(runtime).pipe(
Match.when({ kind: "native" }, () => "native"),
Match.when({ kind: "container" }, ({ engine }) => `container (${engine})`),
Match.exhaustive,
);

const render = (stacks: ReadonlyArray<ReturnType<typeof entry>>): string => {
if (stacks.length === 0) return "No managed stacks found.\n";
const lines = stacks.flatMap((stack, index) => [
...(index === 0 ? [] : [""]),
`${stack.name} (${stack.id})`,
` Project: ${stack.project_root}`,
` Branch: ${stack.branch_context}`,
` Runtime: ${renderRuntime(stack.runtime)}`,
` Desired lifecycle: ${stack.desired_lifecycle}`,
]);
return `${lines.join("\n")}\n`;
};
Comment thread
jgoux marked this conversation as resolved.

export const legacyExperimentalStackList = Effect.fn("legacy.experimental.stack.list")(
function* () {
const output = yield* Output;
const legacyOutput = yield* Effect.serviceOption(LegacyOutputFlag);
if (Option.isSome(legacyOutput) && Option.isSome(legacyOutput.value))
return yield* new LegacyExperimentalStackListError({
reason: "flags",
message: "The legacy -o/--output flag is not supported here; use --output-format json.",
suggestion: "Use --output-format json or --output-format text.",
});
Comment thread
jgoux marked this conversation as resolved.
const api = yield* LegacyExperimentalStackApi;
const stacks = (yield* api.listStacks().pipe(Effect.mapError(mapStackError)))
.map(entry)
.sort(compareEntries);
Comment thread
jgoux marked this conversation as resolved.
if (output.format === "text") yield* output.raw(render(stacks));
else yield* output.success("", { stacks });
return stacks;
},
);
Loading
Loading