Files
webstudio/apps/builder/backend/lib.node.test.ts
Oleg Isonen 4273b7001f refactor: Organize Builder development and test tooling (#6285)
## Outcome

Builder development, backend, E2E, and CI tooling now share one
ownership model: Builder defines named commands and backend modes, while
CI only schedules those commands.

## Technical choices

- Group shared Compose files, PostgreSQL initialization, and backend
helpers under `apps/builder/backend`.
- Resolve `compose.<mode>.yaml` by convention instead of maintaining
Compose filename lists in scripts and workflows.
- Keep persistent development and disposable test Compose projects
separate by default, while allowing the standard `COMPOSE_PROJECT_NAME`
override.
- Make schema snapshots invalidate from discovered migration sources and
backend files.
- Move E2E behavior recipes into named Builder commands; retain concise
root proxies for developer entry points.
- Let Vitest allocate available browser ports instead of maintaining a
cross-package port registry.
- Keep test-suite scheduling explicit in `main.yml`, but move executable
test commands to root package scripts.
- Move fixture-size enforcement from inline GitHub JavaScript into a
locally runnable repository script.

## Todo

- [x] Consolidate Builder backend and Compose ownership.
- [x] Add named development, E2E, schema-cache, Supabase, and CI-shard
commands.
- [x] Remove Compose topology and behavior flags from CI.
- [x] Remove the static Vitest browser-port registry.
- [x] Extract fixture-size checking from the workflow.
- [x] Verify migration bootstrap, snapshot-backed E2E, tests, builds,
types, formatting, lint, generated contracts, and workflow syntax.
- [x] Review documentation impact: updated the E2E developer guide; no
user-facing `docs/` content is affected.

## Verification

- Full package, CLI, Builder core, and Builder app test suites
- Full workspace typecheck
- Builder production build
- Schema-cache generation through the complete migration history
- Supabase migration compatibility
- Snapshot-backed Playwright animation workflow
- Backend regression tests, including demonstrated fail/pass coverage
- All four Compose mode configurations
- `oxfmt`, `oxlint`, package boundaries, generated API, and generated
docs checks
- `actionlint` and Bash syntax checks

The fixture-size checker preserves the existing Linux `du` thresholds.
Its three inputs build locally; macOS allocation sizes are not directly
comparable to the Linux CI thresholds, so CI remains the authoritative
size result.
2026-08-31 21:09:54 +02:00

121 lines
3.2 KiB
TypeScript

import { execFile } from "node:child_process";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import { afterEach, expect, test } from "vitest";
const execFileAsync = promisify(execFile);
const backendLibrary = join(import.meta.dirname, "lib.sh");
const temporaryDirectories: string[] = [];
const createRepositoryFixture = async () => {
const root = await mkdtemp(join(tmpdir(), "webstudio-builder-backend-"));
temporaryDirectories.push(root);
await mkdir(join(root, "packages/prisma-client/prisma/migrations/initial"), {
recursive: true,
});
await mkdir(join(root, "apps/builder/backend"), { recursive: true });
await writeFile(
join(
root,
"packages/prisma-client/prisma/migrations/initial/migration.sql"
),
"SELECT 1;"
);
await writeFile(join(root, "apps/builder/backend/compose.yaml"), "services:");
await writeFile(
join(root, "apps/builder/backend/compose.development.yaml"),
"services:"
);
await writeFile(
join(root, "apps/builder/backend/compose.test.yaml"),
"services:"
);
return root;
};
const readSchemaFingerprint = async (root: string) => {
const { stdout } = await execFileAsync(
"bash",
[
"-c",
'source "$1"; ROOT_DIR="$2"; builder_backend_schema_fingerprint',
"bash",
backendLibrary,
root,
],
{ cwd: root }
);
return stdout.trim();
};
afterEach(async () => {
await Promise.all(
temporaryDirectories
.splice(0)
.map((directory) => rm(directory, { recursive: true, force: true }))
);
});
test("schema fingerprint includes new backend files", async () => {
const root = await createRepositoryFixture();
const before = await readSchemaFingerprint(root);
await writeFile(join(root, "apps/builder/backend/init.sql"), "SELECT 2;");
expect(await readSchemaFingerprint(root)).not.toBe(before);
});
test("backend modes select safe Compose projects", async () => {
const root = await createRepositoryFixture();
const readProjectName = async (mode: "development" | "test") => {
const { stdout } = await execFileAsync(
"bash",
[
"-c",
[
"unset COMPOSE_PROJECT_NAME",
'source "$1"',
'ROOT_DIR="$2"',
'builder_backend_init "$3"',
'printf "%s" "$COMPOSE_PROJECT_NAME"',
].join("; "),
"bash",
backendLibrary,
root,
mode,
],
{ cwd: root }
);
return stdout;
};
expect(await readProjectName("development")).toBe("builder");
expect(await readProjectName("test")).toBe("builder-e2e");
});
test("migration bootstrap requires the Builder schema", async () => {
const root = await createRepositoryFixture();
await expect(
execFileAsync(
"bash",
[
"-c",
[
'source "$1"',
'ROOT_DIR="$2"',
"pnpm() { :; }",
"builder_backend_schema_exists() { return 1; }",
"builder_backend_migrate",
].join("; "),
"bash",
backendLibrary,
root,
],
{ cwd: root }
)
).rejects.toThrow("Migrations completed without creating the Builder schema");
});