Skip to content

Commit 933f262

Browse files
7ttpColy010
andauthored
feat(cli): port init (#5433)
## TL;DR `supabase init` now runs natively in ts ## What’s introduced adds a shared native `init` implementation the command now creates the local project directly in TypeScript, including `supabase/config.toml`, `supabase/.gitignore`, and the optional VS Code and IntelliJ settings files, while preserving existing behavior around `--force`, `--interactive`, legacy hidden IDE flags, and the `--experimental` requirement for `--use-orioledb`..... & coverage all around this! ## Ref - closes CLI-1303 --------- Co-authored-by: Colum Ferry <cferry09@gmail.com>
1 parent 85dfedd commit 933f262

17 files changed

Lines changed: 1597 additions & 159 deletions

apps/cli/docs/go-cli-porting-status.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ These commands exist in the TS CLI today but have no direct top-level equivalent
6565

6666
| Old command | TS status | TS command path or `missing` | Missing flags/params | Extra TS flags/params | Notes |
6767
| ----------- | --------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
68-
| `init` | `partial` | [`../src/next/commands/init/init.command.ts`](../src/next/commands/init/init.command.ts) | `--force`, `--interactive`, `--use-orioledb` | `-` | TS init creates a minimal `supabase/config.json` with only a `"$schema"` reference and ensures repo-local `.supabase/` state can stay gitignored, but it does not yet expose the old Go flag surface. |
68+
| `init` | `ported` | [`../src/next/commands/init/init.command.ts`](../src/next/commands/init/init.command.ts) | `-` | `-` | TS init now writes the Go-style `supabase/config.toml` scaffold, updates `supabase/.gitignore` only when invoked inside a git repo, supports `--force`, `--interactive`, and `--use-orioledb`, and shares the same native implementation with the legacy shell command. |
6969
| `link` | `partial` | [`../src/next/commands/link/link.command.ts`](../src/next/commands/link/link.command.ts) | `--password`, `--skip-pooler` | `-` | TS link supports `--project-ref`, interactive project selection, and zero-config linking. It stores linked remote metadata in repo-local `.supabase/project.json`, but it does not yet manage direct database-password or pooler-specific link flows. |
7070
| `unlink` | `ported` | [`../src/next/commands/unlink/unlink.command.ts`](../src/next/commands/unlink/unlink.command.ts) | `-` | `-` | TS unlink matches the current Go surface and removes the repo-local linked project metadata for the active checkout. |
7171
| `login` | `ported` | [`../src/next/commands/login/login.command.ts`](../src/next/commands/login/login.command.ts) | `-` | `-` | Flag surface matches the old CLI: `--token`, `--name`, `--no-browser`. TS also supports env-var and piped-stdin token input without adding new flags. |
@@ -266,7 +266,7 @@ Legend:
266266
| `link` | `ported` | [`../src/legacy/commands/link/link.command.ts`](../src/legacy/commands/link/link.command.ts) |
267267
| `unlink` | `ported` | [`../src/legacy/commands/unlink/unlink.command.ts`](../src/legacy/commands/unlink/unlink.command.ts) |
268268
| `bootstrap` | `wrapped` | [`../src/legacy/commands/bootstrap/bootstrap.command.ts`](../src/legacy/commands/bootstrap/bootstrap.command.ts) |
269-
| `init` | `wrapped` | [`../src/legacy/commands/init/init.command.ts`](../src/legacy/commands/init/init.command.ts) |
269+
| `init` | `ported` | [`../src/legacy/commands/init/init.command.ts`](../src/legacy/commands/init/init.command.ts) |
270270
| `services` | `wrapped` | [`../src/legacy/commands/services/services.command.ts`](../src/legacy/commands/services/services.command.ts) |
271271
| `start` | `wrapped` | [`../src/legacy/commands/start/start.command.ts`](../src/legacy/commands/start/start.command.ts) |
272272
| `stop` | `wrapped` | [`../src/legacy/commands/stop/stop.command.ts`](../src/legacy/commands/stop/stop.command.ts) |

apps/cli/src/legacy/commands/init/SIDE_EFFECTS.md

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,46 @@
22

33
## Files Read
44

5-
| Path | Format | When |
6-
| ---- | ------ | ---- |
7-
||||
5+
| Path | Format | When |
6+
| ------------------------- | ---------- | ------------------------------------------------------------------------------------------------ |
7+
| `supabase/config.toml` | TOML | checked first to fail fast unless `--force` is set |
8+
| `.git/` | directory | checked upward from the invocation cwd to decide whether `supabase/.gitignore` should be managed |
9+
| `supabase/.gitignore` | text | only when inside a git repo and the file already exists |
10+
| `.vscode/settings.json` | JSONC/JSON | when VS Code settings are generated and the file already exists |
11+
| `.vscode/extensions.json` | JSONC/JSON | when VS Code settings are generated and the file already exists |
812

913
## Files Written
1014

11-
| Path | Format | When |
12-
| ------------------------- | ------ | -------------------------------------------------------- |
13-
| `supabase/config.toml` | TOML | always on success; created from default template |
14-
| `supabase/.gitignore` | text | always on success; gitignores runtime state |
15-
| `.vscode/settings.json` | JSON | when `--with-vscode-settings` flag is set (deprecated) |
16-
| `.vscode/extensions.json` | JSON | when `--with-vscode-workspace` flag is set (deprecated) |
17-
| `.idea/deno.xml` | XML | when `--with-intellij-settings` flag is set (deprecated) |
15+
| Path | Format | When |
16+
| ------------------------- | ------ | --------------------------------------------------------------------------------------------------------------- |
17+
| `supabase/config.toml` | TOML | always on success; created from default template |
18+
| `supabase/.gitignore` | text | when inside a git repo and the template is not already present |
19+
| `.vscode/settings.json` | JSON | when interactive VS Code setup is accepted, or when `--with-vscode-settings` / `--with-vscode-workspace` is set |
20+
| `.vscode/extensions.json` | JSON | when interactive VS Code setup is accepted, or when `--with-vscode-settings` / `--with-vscode-workspace` is set |
21+
| `.idea/deno.xml` | XML | when interactive IntelliJ setup is accepted, or when `--with-intellij-settings` is set |
1822

1923
## API Routes
2024

2125
| Method | Path | Auth | Request body | Response (used fields) |
2226
| ------ | ---- | ---- | ------------ | ---------------------- |
23-
| | | | | |
27+
| - | - | - | - | - |
2428

2529
## Environment Variables
2630

27-
| Variable | Purpose | Required? |
28-
| --------- | ------------------------------------------ | --------- |
29-
| `WORKDIR` | overrides working directory (set to `"."`) | no |
31+
None.
3032

3133
## Exit Codes
3234

33-
| Code | Condition |
34-
| ---- | -------------------------------------------------------------------- |
35-
| `0` | success — prints "Finished supabase init." |
36-
| `1` | `supabase/config.toml` already exists and `--force` was not provided |
37-
| `1` | permission denied writing config file |
35+
| Code | Condition |
36+
| ---- | ------------------------------------------------------------------------------------ |
37+
| `0` | success - prints "Finished supabase init." |
38+
| `1` | `supabase/config.toml` already exists and `--force` was not provided |
39+
| `1` | permission denied writing config file |
40+
| `1` | an existing `.vscode/settings.json` / `.vscode/extensions.json` is not valid JSON(C) |
3841

3942
## Output
4043

41-
### `--output-format text` (Go CLI compatible)
44+
### Legacy Output
4245

4346
On success:
4447

@@ -48,19 +51,15 @@ Finished supabase init.
4851

4952
In interactive mode (`-i`/`--interactive`), may prompt for IDE settings preferences.
5053

51-
### `--output-format json`
52-
53-
Not applicable — init produces no machine-readable output.
54-
55-
### `--output-format stream-json`
56-
57-
Not applicable — init produces no structured output.
54+
Success is emitted as raw text even when the legacy shell is invoked with non-text output modes.
5855

5956
## Notes
6057

61-
- Sets `WORKDIR` to `"."` in `PersistentPreRunE` to prevent recursing to parent directories.
58+
- Uses the invocation cwd directly and does not recurse upward looking for an existing project.
6259
- The `--force` flag overwrites an existing `supabase/config.toml`.
6360
- The `--use-orioledb` flag sets `UseOrioleDB` in init params; requires `--experimental` flag.
6461
- The `--interactive` / `-i` flag enables IDE settings prompts (only effective in TTY).
65-
- The `--with-vscode-settings`, `--with-vscode-workspace`, and `--with-intellij-settings` flags are hidden backward-compat aliases.
66-
- No authentication required — purely local file creation.
62+
- The `--with-vscode-settings` and `--with-vscode-workspace` flags are hidden backward-compat aliases for the same VS Code helper and both write `.vscode/settings.json` and `.vscode/extensions.json`.
63+
- The `--with-intellij-settings` flag is a hidden backward-compat alias for generating `.idea/deno.xml`.
64+
- An existing `.vscode/settings.json` / `.vscode/extensions.json` is parsed tolerantly through a JSONC boundary that strips line/block comments and trailing commas (matching Go's `jsonc.ToJSONInPlace`), then the template is merged on top (template keys win). An empty file is treated as absent and the template is written verbatim. A non-empty file that is not valid JSON(C) aborts the command with `InitParseSettingsError` and is left untouched rather than being overwritten.
65+
- No authentication required - purely local file creation.

apps/cli/src/legacy/commands/init/init.command.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { Command, Flag } from "effect/unstable/cli";
22
import type * as CliCommand from "effect/unstable/cli/Command";
3+
import { withJsonErrorHandling } from "../../../shared/output/json-error-handling.ts";
4+
import { commandRuntimeLayer } from "../../../shared/runtime/command-runtime.layer.ts";
5+
import { withLegacyCommandInstrumentation } from "../../telemetry/legacy-command-instrumentation.ts";
36
import { legacyInit } from "./init.handler.ts";
47

58
const config = {
@@ -32,5 +35,8 @@ export type LegacyInitFlags = CliCommand.Command.Config.Infer<typeof config>;
3235
export const legacyInitCommand = Command.make("init", config).pipe(
3336
Command.withDescription("Initialize a local project."),
3437
Command.withShortDescription("Initialize a local project"),
35-
Command.withHandler((flags) => legacyInit(flags)),
38+
Command.withHandler((flags) =>
39+
legacyInit(flags).pipe(withLegacyCommandInstrumentation({ flags }), withJsonErrorHandling),
40+
),
41+
Command.provide(commandRuntimeLayer(["init"])),
3642
);
Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,47 @@
1-
import { Effect } from "effect";
2-
import { LegacyGoProxy } from "../../../shared/legacy/go-proxy.service.ts";
1+
import { resolve } from "node:path";
2+
import { Effect, Option } from "effect";
3+
import { RuntimeInfo } from "../../../shared/runtime/runtime-info.service.ts";
4+
import { initProject } from "../../../shared/init/project-init.ts";
5+
import {
6+
InitAlreadyExistsError,
7+
InitExperimentalRequiredError,
8+
} from "../../../shared/init/project-init.errors.ts";
9+
import { Output } from "../../../shared/output/output.service.ts";
10+
import { LegacyExperimentalFlag, LegacyWorkdirFlag } from "../../../shared/legacy/global-flags.ts";
311
import type { LegacyInitFlags } from "./init.command.ts";
412

513
export const legacyInit = Effect.fn("legacy.init")(function* (flags: LegacyInitFlags) {
6-
const proxy = yield* LegacyGoProxy;
7-
const args: string[] = ["init"];
8-
if (flags.interactive) args.push("--interactive");
9-
if (flags.useOrioledb) args.push("--use-orioledb");
10-
if (flags.force) args.push("--force");
11-
if (flags.withVscodeWorkspace) args.push("--with-vscode-workspace");
12-
if (flags.withVscodeSettings) args.push("--with-vscode-settings");
13-
if (flags.withIntellijSettings) args.push("--with-intellij-settings");
14-
yield* proxy.exec(args);
14+
const output = yield* Output;
15+
const runtimeInfo = yield* RuntimeInfo;
16+
const experimental = yield* LegacyExperimentalFlag;
17+
const workdir = yield* LegacyWorkdirFlag;
18+
19+
if (flags.useOrioledb && !experimental) {
20+
return yield* Effect.fail(
21+
new InitExperimentalRequiredError({
22+
detail: "--use-orioledb is only available when experimental features are enabled.",
23+
suggestion: "Rerun the command with `--experimental --use-orioledb`.",
24+
}),
25+
);
26+
}
27+
28+
const result = yield* initProject({
29+
cwd: Option.isSome(workdir) ? resolve(runtimeInfo.cwd, workdir.value) : runtimeInfo.cwd,
30+
force: flags.force,
31+
useOrioledb: flags.useOrioledb,
32+
interactive: flags.interactive,
33+
withVscodeSettings: flags.withVscodeWorkspace || flags.withVscodeSettings,
34+
withIntellijSettings: flags.withIntellijSettings,
35+
});
36+
37+
if (!result.created) {
38+
return yield* Effect.fail(
39+
new InitAlreadyExistsError({
40+
detail: `Config already exists at ${result.configPath}.`,
41+
suggestion: "Run `supabase init --force` to overwrite the existing config.",
42+
}),
43+
);
44+
}
45+
46+
yield* output.raw("Finished supabase init.\n");
1547
});

0 commit comments

Comments
 (0)