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
2 changes: 1 addition & 1 deletion apps/cli/docs/go-cli-porting-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ Legend:
| `functions delete` | `ported` | [`../src/legacy/commands/functions/delete/delete.command.ts`](../src/legacy/commands/functions/delete/delete.command.ts) |
| `functions download` | `ported` | [`../src/legacy/commands/functions/download/download.command.ts`](../src/legacy/commands/functions/download/download.command.ts) |
| `functions deploy` | `ported` | [`../src/legacy/commands/functions/deploy/deploy.command.ts`](../src/legacy/commands/functions/deploy/deploy.command.ts) |
| `functions new` | `wrapped` | [`../src/legacy/commands/functions/new/new.command.ts`](../src/legacy/commands/functions/new/new.command.ts) |
| `functions new` | `ported` | [`../src/legacy/commands/functions/new/new.command.ts`](../src/legacy/commands/functions/new/new.command.ts) |
| `functions serve` | `ported` | [`../src/legacy/commands/functions/serve/serve.command.ts`](../src/legacy/commands/functions/serve/serve.command.ts) |
| `storage ls` | `wrapped` | [`../src/legacy/commands/storage/ls/ls.command.ts`](../src/legacy/commands/storage/ls/ls.command.ts) |
| `storage cp` | `wrapped` | [`../src/legacy/commands/storage/cp/cp.command.ts`](../src/legacy/commands/storage/cp/cp.command.ts) |
Expand Down
70 changes: 49 additions & 21 deletions apps/cli/src/legacy/commands/functions/new/SIDE_EFFECTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,81 @@

## Files Read

| Path | Format | When |
| ---- | ------ | ---- |
| — | — | — |
| Path | Format | When |
| ----------------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `~/.supabase/profile` | plain text | when `--profile` and `SUPABASE_PROFILE` are both unset |
| `<profile>.yaml` | YAML | when `SUPABASE_PROFILE` or `--profile` points to a file |
| `<workdir>/supabase/config.toml` | TOML | best-effort when resolving template values, detecting existing `[functions.<name>]` declarations, and scanning declared function slugs |
| `<workdir>/supabase/functions/*/index.ts` | TypeScript | when checking whether this is the first local function |
| `<workdir>/.vscode/extensions.json` | JSONC | when merging VS Code recommendations into an existing file |
| `<workdir>/.vscode/settings.json` | JSONC | when merging Deno settings into an existing file |
| `<SUPABASE_HOME or ~/.supabase>/telemetry.json` | JSON | when present, before post-run telemetry state is refreshed |

## Files Written

| Path | Format | When |
| ---------------------------------------------- | ---------- | ---------------------------------------------------------------- |
| `<workdir>/supabase/functions/<name>/index.ts` | TypeScript | always (creates function scaffold, template depends on `--auth`) |
| Path | Format | When |
| ----------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------ |
| `<workdir>/supabase/functions/<name>/index.ts` | TypeScript | always |
| `<workdir>/supabase/functions/<name>/deno.json` | JSON | always |
| `<workdir>/supabase/functions/<name>/.npmrc` | plain text | always |
| `<workdir>/supabase/config.toml` | TOML | always unless `[functions.<name>]` is already declared |
| `<workdir>/.vscode/extensions.json` | JSON | text mode only, when this is the first function and VS Code settings are accepted or auto-accepted |
| `<workdir>/.vscode/settings.json` | JSON | text mode only, when this is the first function and VS Code settings are accepted or auto-accepted |
| `<workdir>/.idea/deno.xml` | XML | text mode only, when this is the first function, VS Code settings are declined, and IntelliJ settings are accepted |
| `<SUPABASE_HOME or ~/.supabase>/telemetry.json` | JSON | after command completion, flushed on both success and failure paths |

## API Routes

| Method | Path | Auth | Request body | Response (used fields) |
| ------ | ---- | ---- | ------------ | ---------------------- |
| — | — | — | — | — |
| Method | Path | Auth | Request body | Response (used fields) |
| ------ | ------ | ------ | ------------ | ---------------------- |
| `none` | `none` | `none` | `none` | `none` |

## Environment Variables

| Variable | Purpose | Required? |
| -------- | ------- | --------- |
| — | — | — |
| Variable | Purpose | Required? |
| ----------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- |
| `SUPABASE_ACCESS_TOKEN` | resolved into legacy CLI config even though this command performs no API calls | no (falls back to credential lookup paths that are not used here) |
| `SUPABASE_HOME` | changes where telemetry state is persisted | no (defaults to `~/.supabase`) |
| `SUPABASE_PROFILE` | selects a built-in profile or YAML profile path during legacy CLI config resolution | no (falls back to `~/.supabase/profile` -> `supabase`) |
| `SUPABASE_PROJECT_ID` | resolved into legacy CLI config even though this command does not use a linked project ref | no |
| `SUPABASE_WORKDIR` | sets `<workdir>` for all local project reads and writes | no (falls back to `--workdir` -> current working dir) |

## Exit Codes

| Code | Condition |
| ---- | --------------------------------- |
| `0` | success |
| `1` | invalid function name |
| `1` | function directory already exists |
| Code | Condition |
| ---- | ---------------------------------- |
| `0` | success |
| `1` | invalid function name |
| `1` | function entrypoint already exists |
| `1` | local file write failed |

## Telemetry Events Fired

| Event | When | Notable properties / groups |
| ---------------------- | ------------------------------------------ | ----------------------------------- |
| `cli_command_executed` | post-run, success or failure (via wrapper) | `exit_code`, `duration_ms`, `flags` |

## Output

### `--output-format text` (Go CLI compatible)

Prints a success message with the path to the created function file.
Prints `Created new Function at <path>` and, when this is the first function, may also print the IDE prompt plus generated settings messages.

### `--output-format json`

Not applicable (proxied to Go binary).
Emits a structured success payload with `path`, `function_name`, and `auth`. No IDE settings are scaffolded and no IDE prompt is printed — machine formats are payload-only.

### `--output-format stream-json`

Not applicable (proxied to Go binary).
Emits a structured success result event with `path`, `function_name`, and `auth`. No IDE settings are scaffolded and no IDE prompt is printed — machine formats are payload-only.

## Notes

- Creates a new Edge Function scaffold locally.
- Requires exactly one argument: the function name.
- `--auth` selects the auth-mode template (`none` | `apikey` | `user`, default: `apikey`).
- Phase 0 proxy: all invocations are forwarded to the bundled Go binary.
- Best-effort config parsing is intentionally non-fatal here: malformed `config.toml` does not block scaffolding or config append, matching the Go command.
- The `[functions.<name>]` config section is **appended** (`O_APPEND` semantics, `flag: "a"`), never rewritten, so the existing file is left byte-for-byte untouched and a partial write cannot truncate it — matching Go's `appendConfigFile`.
- Existing-declaration detection scans the raw `config.toml` text (`^\s*\[functions\.<slug>\]\s*$`) rather than the parsed config map Go uses. This is a deliberate divergence: config loading here is non-fatal, so a raw-text scan stays deterministic even when the file fails to parse. For all well-formed configs the two approaches agree.
- IDE settings scaffolding (`.vscode`, `.idea`) only runs in `--output-format text`; json / stream-json runs are payload-only.
- No Management API requests are made; all behavior is local filesystem work plus telemetry flush.
25 changes: 23 additions & 2 deletions apps/cli/src/legacy/commands/functions/new/new.command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { Layer } from "effect";
import { Argument, 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 { commandRuntimeLayer } from "../../../../shared/runtime/command-runtime.layer.ts";
import { legacyCliConfigLayer } from "../../../config/legacy-cli-config.layer.ts";
import { legacyDebugLoggerLayer } from "../../../shared/legacy-debug-logger.layer.ts";
import { legacyTelemetryStateLayer } from "../../../telemetry/legacy-telemetry-state.layer.ts";
import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts";
import { legacyFunctionsNew } from "./new.handler.ts";

const AUTH_MODE_VALUES = ["none", "apikey", "user"] as const;
Expand All @@ -10,14 +17,28 @@ const config = {
),
auth: Flag.choice("auth", AUTH_MODE_VALUES).pipe(
Flag.withDescription("use a specific auth mode"),
Flag.optional,
Flag.withDefault("apikey" as const),
),
} as const;

export type LegacyFunctionsNewFlags = CliCommand.Command.Config.Infer<typeof config>;

const cliConfig = legacyCliConfigLayer.pipe(Layer.provide(legacyDebugLoggerLayer));

const legacyFunctionsNewRuntimeLayer = Layer.mergeAll(
cliConfig,
legacyTelemetryStateLayer,
commandRuntimeLayer(["functions", "new"]),
);

export const legacyFunctionsNewCommand = Command.make("new", config).pipe(
Command.withDescription("Create a new Function locally."),
Command.withShortDescription("Create a new Function locally"),
Command.withHandler((flags) => legacyFunctionsNew(flags)),
Command.withHandler((flags) =>
legacyFunctionsNew(flags).pipe(
withLegacyCommandInstrumentation({ flags }),
withJsonErrorHandling,
),
),
Command.provide(legacyFunctionsNewRuntimeLayer),
);
38 changes: 38 additions & 0 deletions apps/cli/src/legacy/commands/functions/new/new.errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Data } from "effect";

export class LegacyFunctionsNewInvalidSlugError extends Data.TaggedError(
"LegacyFunctionsNewInvalidSlugError",
)<{
readonly message: string;
readonly detail: string;
}> {}

export class LegacyFunctionsNewFileExistsError extends Data.TaggedError(
"LegacyFunctionsNewFileExistsError",
)<{
readonly path: string;
readonly message: string;
readonly suggestion: string;
}> {}

export class LegacyFunctionsNewWriteError extends Data.TaggedError("LegacyFunctionsNewWriteError")<{
readonly path: string;
readonly message: string;
}> {}

/**
* Maps an arbitrary thrown cause from a filesystem write to a typed
* `LegacyFunctionsNewWriteError` tagged with the given `path`. Used by the IDE
* settings writers, where the same shape is needed for both the `.vscode` and
* `.idea/deno.xml` targets.
*/
export function mapLegacyFunctionsNewWriteError(path: string) {
return (cause: unknown): LegacyFunctionsNewWriteError =>
new LegacyFunctionsNewWriteError({
path,
message:
typeof cause === "object" && cause !== null && "message" in cause
? String(cause.message)
: String(cause),
});
}
Loading
Loading