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
29 changes: 23 additions & 6 deletions apps/cli/src/commands/experimental/compute/new/SIDE_EFFECTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,29 @@ and the command handler does not run. See the [Compute command guide](../../../.

## Files Written

| Path | Format | When |
| ----------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `<workdir>/supabase/config.toml` | TOML | on success — appends `[compute.<name>]` with `runtime`, `size` and `exposure` always, `instances` only when it differs from the default of 1, and `source` only when `--source` was passed, preserving surrounding formatting |
| `<workdir>/supabase/compute/<name>/*` | varies | on success, unless `--source` names another directory |
| `<workdir>/<source>/*` | varies | on success, when `--source` is given |
| `<SUPABASE_HOME or ~/.supabase>/telemetry.json` | JSON | whenever the handler runs — flushed on success and on failure |
| Path | Format | When |
| ----------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `<workdir>/supabase/config.toml` | TOML | on success — appends `[compute.<name>]` with `runtime`, `size` and `exposure` always, `exclude` whenever the chosen runtime declares default patterns, `instances` only when it differs from the default of 1, and `source` only when `--source` was passed, preserving surrounding formatting |
| `<workdir>/supabase/compute/<name>/*` | varies | on success, unless `--source` names another directory |
| `<workdir>/<source>/*` | varies | on success, when `--source` is given |
| `<SUPABASE_HOME or ~/.supabase>/telemetry.json` | JSON | whenever the handler runs — flushed on success and on failure |

## Default `exclude` patterns

Each runtime declares the `[compute.<name>] exclude` patterns a scaffold starts with, and they
are written into `config.toml` as a single-line TOML array rather than applied silently at push
time — the list is the runtime's opinion about its own build, and a file the user can read and
edit is the only place that opinion can be argued with. `push` has no built-in defaults of its
own, so editing or deleting the line is all it takes to change what ships.

Every runtime excludes environment files and version-control metadata (`.env`, `.env.*`,
`.git`, matching a worktree's `.git` file as well as a repository's directory). Beyond that the two catalog runtimes carry more than `dockerfile` does, because the
CLI knows what tooling writes into their directories: `node` also drops `node_modules/` and
`*.log`, `deno` drops `*.log`, and a `dockerfile` context is left alone because the user's own
`Dockerfile` already decides what it copies. The key is
omitted entirely for a runtime that declares no patterns. The chosen list is reported as the
`Excluded` row beside the compute's other dials, and carried on the machine-output payload as
`exclude`.

Compute resources are recorded in `config.toml` only. The project config loader prefers
`supabase/config.json` when one exists, but the entry writer is a TOML text
Expand Down
10 changes: 10 additions & 0 deletions apps/cli/src/commands/experimental/compute/new/new.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
COMPUTE_EXPOSURE_DESCRIPTIONS,
COMPUTE_EXPOSURES,
COMPUTE_RUNTIME_DESCRIPTIONS,
COMPUTE_RUNTIME_EXCLUSIONS,
COMPUTE_RUNTIMES,
COMPUTE_SIZES,
type ComputeExposure,
Expand Down Expand Up @@ -273,6 +274,10 @@ export const computeNew = Effect.fn("compute.new")(function* (flags: ComputeNewF
const size = yield* resolveSize({ explicit: flags.size, canPrompt });
const exposure = yield* resolveExposure({ explicit: flags.exposure, canPrompt });
const instances = recordedInstances(flags.instances);
// The chosen runtime's own defaults, written down rather than applied invisibly at push
// time: the list is the runtime's opinion about its build, and a `config.toml` the user
// can read and edit is the only place that opinion can be argued with.
const exclude = COMPUTE_RUNTIME_EXCLUSIONS[runtime];

// Validated before anything is written: this is the directory the starter files
// land in, so a value naming the project root, `supabase/`, or anywhere outside
Expand Down Expand Up @@ -331,6 +336,7 @@ export const computeNew = Effect.fn("compute.new")(function* (flags: ComputeNewF
exposure,
...(instances === undefined ? {} : { instances }),
...(source === undefined ? {} : { source }),
...(exclude.length === 0 ? {} : { exclude }),
},
});

Expand Down Expand Up @@ -363,6 +369,7 @@ export const computeNew = Effect.fn("compute.new")(function* (flags: ComputeNewF
// than "one".
instances: instances ?? DEFAULT_COMPUTE_INSTANCES,
source: sourceDisplay,
exclude,
config_path: project.configPath,
};

Expand Down Expand Up @@ -391,6 +398,9 @@ export const computeNew = Effect.fn("compute.new")(function* (flags: ComputeNewF
// `declared`, the way `compute status` labels the same number: nothing
// is running yet, so a bare count would read as a live tally.
["Instances", `${instances ?? DEFAULT_COMPUTE_INSTANCES} declared`],
// Shown because the scaffold decided it: a compute that silently leaves files out of
// its deploy should say so where the rest of its dials are reported.
["Excluded", exclude.join(", ")],
]),
);
// On the success trailer rather than inline, the way `bootstrap` emits its
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BunServices } from "@effect/platform-bun";
import { describe, expect, it } from "@effect/vitest";
import { Effect, Option, FileSystem, Path, Predicate, Schema } from "effect";
import * as SmolToml from "smol-toml";
import { makeComputeProject, setupCompute } from "../../../../../tests/helpers/compute.ts";
import {
ComputeAlreadyConfiguredError,
Expand All @@ -13,6 +14,10 @@ import {
ComputeDirectoryExistsError,
ComputeJsonConfigUnsupportedError,
} from "../../../../shared/compute/compute.errors.ts";
import {
COMPUTE_RUNTIME_EXCLUSIONS,
type ComputeRuntime,
} from "../../../../shared/compute/compute-runtimes.ts";
import { computeNew } from "./new.handler.ts";
import { ComputeNewWorkdirError } from "./new.errors.ts";
import type { ComputeNewFlags } from "./new.command.ts";
Expand All @@ -24,6 +29,18 @@ project_id = "demo"
verify_jwt = false
`;

/**
* The `exclude = [...]` line `new` writes for a runtime, derived from the runtime's own list
* rather than restated: which patterns a runtime chooses is asserted where the patterns are
* matched, and the claim here is only that the list reaches `config.toml` as a TOML array.
*/
function excludeLine(runtime: ComputeRuntime): string {
const patterns = COMPUTE_RUNTIME_EXCLUSIONS[runtime];
return patterns.length === 0
? ""
: `exclude = [${patterns.map((pattern) => `"${pattern}"`).join(", ")}]\n`;
}

function flags(overrides: Partial<ComputeNewFlags> = {}): ComputeNewFlags {
return {
name: Option.some("api"),
Expand Down Expand Up @@ -63,7 +80,9 @@ describe("compute new", () => {
const computeDir = path.join(repo.dir, "supabase", "compute", "api");
expect(yield* fs.exists(path.join(computeDir, "index.mjs"))).toBe(true);
expect(yield* repo.config).toBe(
`${CONFIG_WITH_COMMENTS}\n[compute.api]\nruntime = "node"\nsize = "2gb"\nexposure = "public"\n`,
`${CONFIG_WITH_COMMENTS}\n[compute.api]\nruntime = "node"\nsize = "2gb"\nexposure = "public"\n${excludeLine(
"node",
)}`,
);

// Declarative line first, then the detail rows, then the next step —
Expand Down Expand Up @@ -308,6 +327,58 @@ describe("compute new", () => {
}).pipe(Effect.scoped, Effect.provide(BunServices.layer)),
);

// Written into config.toml rather than applied invisibly at push time, so the list is
// visible and editable and `push` needs no built-in defaults of its own.
describe("the chosen runtime's default exclude patterns", () => {
it.live.each(["node", "deno", "dockerfile"] as const)(
"records the %s runtime's own list",
(runtime) =>
Effect.gen(function* () {
const repo = yield* project();
const { layer } = setupCompute({ workdir: repo.dir });

return yield* Effect.gen(function* () {
yield* computeNew(flags({ runtime: Option.some(runtime) }));

expect(yield* repo.config).toContain(excludeLine(runtime).trimEnd());
}).pipe(Effect.provide(layer));
}).pipe(Effect.scoped, Effect.provide(BunServices.layer)),
);

// The written entry has to be loadable, or the scaffold leaves behind a project whose
// config nothing can read — the patterns are quoted strings in a TOML array, which is
// exactly the shape a hand-rolled renderer gets wrong.
it.live("writes them as a list the config loader reads back", () =>
Effect.gen(function* () {
const repo = yield* project();
const { layer } = setupCompute({ workdir: repo.dir });

return yield* Effect.gen(function* () {
yield* computeNew(flags({ runtime: Option.some("node") }));

const parsed = SmolToml.parse(yield* repo.config) as {
compute?: { api?: { exclude?: unknown } };
};
expect(parsed.compute?.api?.exclude).toEqual([...COMPUTE_RUNTIME_EXCLUSIONS.node]);
}).pipe(Effect.provide(layer));
}).pipe(Effect.scoped, Effect.provide(BunServices.layer)),
);

it.live("reports them alongside the compute's other dials", () =>
Effect.gen(function* () {
const repo = yield* project();
const { layer, out } = setupCompute({ workdir: repo.dir });

return yield* Effect.gen(function* () {
yield* computeNew(flags({ runtime: Option.some("node") }));

expect(out.stdoutText).toContain("Excluded");
expect(out.stdoutText).toContain(".env");
}).pipe(Effect.provide(layer));
}).pipe(Effect.scoped, Effect.provide(BunServices.layer)),
);
});

// The runtime and size prompts do have defaults to fall back on, so a piped
// stdin must leave them unasked rather than consuming the pipe.
it.live("takes the defaults without prompting when stdin is piped", () =>
Expand Down Expand Up @@ -517,7 +588,7 @@ describe("compute new", () => {
const computeDir = path.join(created.dir, "supabase", "compute", "api");
expect(yield* fs.exists(path.join(computeDir, "index.mjs"))).toBe(true);
expect(yield* fs.readFileString(path.join(created.dir, "supabase", "config.toml"))).toBe(
`[compute.api]\nruntime = "node"\nsize = "2gb"\nexposure = "public"\n`,
`[compute.api]\nruntime = "node"\nsize = "2gb"\nexposure = "public"\n${excludeLine("node")}`,
);
// An EXPLICIT --workdir has no cwd-relative reading, so the success
// message names the absolute path rather than a project-root-relative one.
Expand Down Expand Up @@ -783,7 +854,7 @@ describe("compute new", () => {

// The workdir got both the entry and the scaffold it points at.
expect(yield* fs.readFileString(path.join(workdir, "supabase", "config.toml"))).toBe(
'[compute.api]\nruntime = "node"\nsize = "2gb"\nexposure = "public"\n',
`[compute.api]\nruntime = "node"\nsize = "2gb"\nexposure = "public"\n${excludeLine("node")}`,
);
expect(
yield* fs.exists(path.join(workdir, "supabase", "compute", "api", "index.mjs")),
Expand Down
Loading
Loading