chore(cli): add tests (#576)

This commit is contained in:
Aman Varshney
2025-09-14 11:29:01 +05:30
committed by GitHub
parent d39fa6e306
commit 98a0ab9ee6
29 changed files with 5720 additions and 4611 deletions

View File

@@ -52,9 +52,8 @@
"dev": "tsdown --watch",
"check-types": "tsc --noEmit",
"check": "biome check --write .",
"test": "bun run build && vitest run",
"test": "bun run build && vitest run; rm -rf .smoke || true",
"test:ui": "bun run build && vitest --ui",
"test:with-build": "bun run build && WITH_BUILD=1 vitest --ui",
"prepublishOnly": "npm run build"
},
"exports": {

View File

@@ -174,7 +174,7 @@ export async function createProjectHandler(
);
}
await createProject(config);
await createProject(config, { manualDb: input.manualDb });
const reproducibleCommand = generateReproducibleCommand(config);
log.success(

View File

@@ -32,7 +32,10 @@ import {
setupFrontendTemplates,
} from "./template-manager";
export async function createProject(options: ProjectConfig) {
export async function createProject(
options: ProjectConfig,
cliInput?: { manualDb?: boolean },
) {
const projectDir = options.projectDir;
const isConvex = options.backend === "convex";
@@ -58,7 +61,7 @@ export async function createProject(options: ProjectConfig) {
if (!isConvex) {
await setupBackendDependencies(options);
await setupDatabase(options);
await setupDatabase(options, cliInput);
await setupRuntime(options);
if (options.examples.length > 0 && options.examples[0] !== "none") {
await setupExamples(options);

View File

@@ -14,7 +14,10 @@ import { setupPrismaPostgres } from "../database-providers/prisma-postgres-setup
import { setupSupabase } from "../database-providers/supabase-setup";
import { setupTurso } from "../database-providers/turso-setup";
export async function setupDatabase(config: ProjectConfig) {
export async function setupDatabase(
config: ProjectConfig,
cliInput?: { manualDb?: boolean },
) {
const { database, orm, dbSetup, backend, projectDir } = config;
if (backend === "convex" || database === "none") {
@@ -113,25 +116,25 @@ export async function setupDatabase(config: ProjectConfig) {
if (dbSetup === "docker") {
await setupDockerCompose(config);
} else if (database === "sqlite" && dbSetup === "turso") {
await setupTurso(config);
await setupTurso(config, cliInput);
} else if (database === "sqlite" && dbSetup === "d1") {
await setupCloudflareD1(config);
} else if (database === "postgres") {
if (dbSetup === "prisma-postgres") {
await setupPrismaPostgres(config);
await setupPrismaPostgres(config, cliInput);
} else if (dbSetup === "neon") {
await setupNeonPostgres(config);
await setupNeonPostgres(config, cliInput);
} else if (dbSetup === "planetscale") {
await setupPlanetScale(config);
} else if (dbSetup === "supabase") {
await setupSupabase(config);
await setupSupabase(config, cliInput);
}
} else if (database === "mysql") {
if (dbSetup === "planetscale") {
await setupPlanetScale(config);
}
} else if (database === "mongodb" && dbSetup === "mongodb-atlas") {
await setupMongoDBAtlas(config);
await setupMongoDBAtlas(config, cliInput);
}
} catch (error) {
s.stop(pc.red("Failed to set up database"));

View File

@@ -120,8 +120,12 @@ ${pc.green("MongoDB Atlas Manual Setup Instructions:")}
`);
}
export async function setupMongoDBAtlas(config: ProjectConfig) {
export async function setupMongoDBAtlas(
config: ProjectConfig,
cliInput?: { manualDb?: boolean },
) {
const { projectDir } = config;
const manualDb = cliInput?.manualDb ?? false;
const mainSpinner = spinner();
mainSpinner.start("Setting up MongoDB Atlas...");
@@ -129,6 +133,13 @@ export async function setupMongoDBAtlas(config: ProjectConfig) {
try {
await fs.ensureDir(serverDir);
if (manualDb) {
mainSpinner.stop("MongoDB Atlas manual setup selected");
await writeEnvFile(projectDir);
displayManualSetupInstructions();
return;
}
const mode = await select({
message: "MongoDB Atlas setup: choose mode",
options: [

View File

@@ -154,10 +154,20 @@ function displayManualSetupInstructions() {
DATABASE_URL="your_connection_string"`);
}
export async function setupNeonPostgres(config: ProjectConfig) {
export async function setupNeonPostgres(
config: ProjectConfig,
cliInput?: { manualDb?: boolean },
) {
const { packageManager, projectDir } = config;
const manualDb = cliInput?.manualDb ?? false;
try {
if (manualDb) {
await writeEnvFile(projectDir);
displayManualSetupInstructions();
return;
}
const mode = await select({
message: "Neon setup: choose mode",
options: [

View File

@@ -212,13 +212,23 @@ async function addPrismaAccelerateExtension(serverDir: string) {
}
}
export async function setupPrismaPostgres(config: ProjectConfig) {
export async function setupPrismaPostgres(
config: ProjectConfig,
cliInput?: { manualDb?: boolean },
) {
const { packageManager, projectDir, orm } = config;
const manualDb = cliInput?.manualDb ?? false;
const serverDir = path.join(projectDir, "apps/server");
try {
await fs.ensureDir(serverDir);
if (manualDb) {
await writeEnvFile(projectDir);
displayManualSetupInstructions();
return;
}
const mode = await select({
message: "Prisma Postgres setup: choose mode",
options: [

View File

@@ -152,14 +152,24 @@ ${pc.dim(output)}`
);
}
export async function setupSupabase(config: ProjectConfig) {
export async function setupSupabase(
config: ProjectConfig,
cliInput?: { manualDb?: boolean },
) {
const { projectDir, packageManager } = config;
const manualDb = cliInput?.manualDb ?? false;
const serverDir = path.join(projectDir, "apps", "server");
try {
await fs.ensureDir(serverDir);
if (manualDb) {
displayManualSupabaseInstructions();
await writeSupabaseEnvFile(projectDir, "");
return;
}
const mode = await select({
message: "Supabase setup: choose mode",
options: [

View File

@@ -186,12 +186,22 @@ DATABASE_URL=your_database_url
DATABASE_AUTH_TOKEN=your_auth_token`);
}
export async function setupTurso(config: ProjectConfig) {
export async function setupTurso(
config: ProjectConfig,
cliInput?: { manualDb?: boolean },
) {
const { orm, projectDir } = config;
const manualDb = cliInput?.manualDb ?? false;
const _isDrizzle = orm === "drizzle";
const setupSpinner = spinner();
try {
if (manualDb) {
await writeEnvFile(projectDir);
displayManualSetupInstructions();
return;
}
const mode = await select({
message: "Turso setup: choose mode",
options: [

View File

@@ -99,6 +99,13 @@ export const router = t.router({
.optional()
.default(false)
.describe("Disable analytics"),
manualDb: z
.boolean()
.optional()
.default(false)
.describe(
"Skip automatic/manual database setup prompt and use manual setup",
),
}),
]),
)

View File

@@ -147,6 +147,7 @@ export type CreateInput = {
directoryConflict?: DirectoryConflict;
renderTitle?: boolean;
disableAnalytics?: boolean;
manualDb?: boolean;
};
export type AddInput = {

View File

@@ -2,16 +2,11 @@ import path from "node:path";
import { Biome } from "@biomejs/js-api/nodejs";
import consola from "consola";
let biome: Biome | null = null;
let projectKey: number | null = null;
async function initializeBiome() {
if (biome && projectKey !== null) return { biome, projectKey };
function initializeBiome() {
try {
biome = new Biome();
const biome = new Biome();
const result = biome.openProject("./");
projectKey = result.projectKey;
const projectKey = result.projectKey;
biome.applyConfiguration(projectKey, {
formatter: {
@@ -61,18 +56,18 @@ function shouldSkipFile(filePath: string) {
return skipPatterns.some((pattern) => basename.includes(pattern));
}
export async function formatFileWithBiome(filePath: string, content: string) {
export function formatFileWithBiome(filePath: string, content: string) {
if (!isSupportedFile(filePath) || shouldSkipFile(filePath)) {
return null;
}
try {
const biomeResult = await initializeBiome();
const biomeResult = initializeBiome();
if (!biomeResult) return null;
const { biome: biomeInstance, projectKey: key } = biomeResult;
const { biome, projectKey } = biomeResult;
const result = biomeInstance.formatContent(key, content, {
const result = biome.formatContent(projectKey, content, {
filePath: path.basename(filePath),
});

View File

@@ -380,7 +380,8 @@ export function validateApiConstraints(
if (
options.examples &&
!(options.examples.length === 1 && options.examples[0] === "none") &&
options.backend !== "convex"
options.backend !== "convex" &&
options.backend !== "none"
) {
exitWithError(
"Cannot use '--examples' when '--api' is set to 'none'. Please remove the --examples flag or choose an API type.",

View File

@@ -0,0 +1,338 @@
import { describe, it } from "vitest";
import type { Addons, Frontend } from "../src";
import {
expectError,
expectSuccess,
runTRPCTest,
type TestConfig,
} from "./test-utils";
describe("Addon Configurations", () => {
describe("Universal Addons (no frontend restrictions)", () => {
const universalAddons = ["biome", "husky", "turborepo", "oxlint"];
for (const addon of universalAddons) {
it(`should work with ${addon} addon on any frontend`, async () => {
const result = await runTRPCTest({
projectName: `${addon}-universal`,
addons: [addon as Addons],
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
}
});
describe("Frontend-Specific Addons", () => {
describe("PWA Addon", () => {
const pwaCompatibleFrontends = [
"tanstack-router",
"react-router",
"solid",
"next",
];
for (const frontend of pwaCompatibleFrontends) {
it(`should work with PWA + ${frontend}`, async () => {
const config: TestConfig = {
projectName: `pwa-${frontend}`,
addons: ["pwa"],
frontend: [frontend as Frontend],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
};
// Handle special frontend requirements
if (frontend === "solid") {
config.api = "orpc"; // tRPC not supported with solid
} else {
config.api = "trpc";
}
const result = await runTRPCTest(config);
expectSuccess(result);
});
}
const pwaIncompatibleFrontends = [
"nuxt",
"svelte",
"native-nativewind",
"native-unistyles",
];
for (const frontend of pwaIncompatibleFrontends) {
it(`should fail with PWA + ${frontend}`, async () => {
const config: TestConfig = {
projectName: `pwa-${frontend}-fail`,
addons: ["pwa"],
frontend: [frontend as Frontend],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
};
if (["nuxt", "svelte"].includes(frontend)) {
config.api = "orpc";
} else {
config.api = "trpc";
}
const result = await runTRPCTest(config);
expectError(
result,
"pwa addon requires one of these frontends: tanstack-router, react-router, solid, next",
);
});
}
});
describe("Tauri Addon", () => {
const tauriCompatibleFrontends = [
"tanstack-router",
"react-router",
"nuxt",
"svelte",
"solid",
"next",
];
for (const frontend of tauriCompatibleFrontends) {
it(`should work with Tauri + ${frontend}`, async () => {
const config: TestConfig = {
projectName: `tauri-${frontend}`,
addons: ["tauri"],
frontend: [frontend as Frontend],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
};
if (["nuxt", "svelte", "solid"].includes(frontend)) {
config.api = "orpc";
} else {
config.api = "trpc";
}
const result = await runTRPCTest(config);
expectSuccess(result);
});
}
const tauriIncompatibleFrontends = [
"native-nativewind",
"native-unistyles",
];
for (const frontend of tauriIncompatibleFrontends) {
it(`should fail with Tauri + ${frontend}`, async () => {
const result = await runTRPCTest({
projectName: `tauri-${frontend}-fail`,
addons: ["tauri"],
frontend: [frontend as Frontend],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "tauri addon requires one of these frontends");
});
}
});
});
describe("Multiple Addons", () => {
it("should work with multiple compatible addons", async () => {
const result = await runTRPCTest({
projectName: "multiple-addons",
addons: ["biome", "husky", "turborepo", "pwa"],
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with incompatible addon combination", async () => {
const result = await runTRPCTest({
projectName: "incompatible-addons-fail",
addons: ["pwa"], // PWA not compatible with nuxt
frontend: ["nuxt"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "orpc",
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "pwa addon requires one of these frontends");
});
it("should deduplicate addons", async () => {
const result = await runTRPCTest({
projectName: "duplicate-addons",
addons: ["biome", "biome", "turborepo"], // Duplicate biome
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
});
describe("Addons with None Option", () => {
it("should work with addons none", async () => {
const result = await runTRPCTest({
projectName: "no-addons",
addons: ["none"],
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with none + other addons", async () => {
const result = await runTRPCTest({
projectName: "none-with-other-addons-fail",
addons: ["none", "biome"],
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Cannot combine 'none' with other addons");
});
});
describe("All Available Addons", () => {
const testableAddons = [
"pwa",
"tauri",
"biome",
"husky",
"turborepo",
"oxlint",
// Note: starlight, ultracite, ruler, fumadocs are prompt-controlled only
];
for (const addon of testableAddons) {
it(`should work with ${addon} addon in appropriate setup`, async () => {
const config: TestConfig = {
projectName: `test-${addon}`,
addons: [addon as Addons],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
};
// Choose compatible frontend for each addon
if (["pwa"].includes(addon)) {
config.frontend = ["tanstack-router"]; // PWA compatible
} else if (["tauri"].includes(addon)) {
config.frontend = ["tanstack-router"]; // Tauri compatible
} else {
config.frontend = ["tanstack-router"]; // Universal addons
}
const result = await runTRPCTest(config);
expectSuccess(result);
});
}
});
});

639
apps/cli/test/api.test.ts Normal file
View File

@@ -0,0 +1,639 @@
import { describe, it } from "vitest";
import type {
API,
Backend,
Database,
Examples,
Frontend,
ORM,
Runtime,
} from "../src/types";
import {
API_TYPES,
expectError,
expectSuccess,
runTRPCTest,
type TestConfig,
} from "./test-utils";
describe("API Configurations", () => {
describe("tRPC API", () => {
it("should work with tRPC + React frontends", async () => {
const reactFrontends = [
"tanstack-router",
"react-router",
"tanstack-start",
"next",
];
for (const frontend of reactFrontends) {
const result = await runTRPCTest({
projectName: `trpc-${frontend}`,
api: "trpc",
frontend: [frontend as Frontend],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
}
});
it("should work with tRPC + native frontends", async () => {
const nativeFrontends = ["native-nativewind", "native-unistyles"];
for (const frontend of nativeFrontends) {
const result = await runTRPCTest({
projectName: `trpc-${frontend}`,
api: "trpc",
frontend: [frontend as Frontend],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
}
});
it("should fail with tRPC + Nuxt", async () => {
const result = await runTRPCTest({
projectName: "trpc-nuxt-fail",
api: "trpc",
frontend: ["nuxt"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "tRPC API is not supported with 'nuxt' frontend");
});
it("should fail with tRPC + Svelte", async () => {
const result = await runTRPCTest({
projectName: "trpc-svelte-fail",
api: "trpc",
frontend: ["svelte"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "tRPC API is not supported with 'svelte' frontend");
});
it("should fail with tRPC + Solid", async () => {
const result = await runTRPCTest({
projectName: "trpc-solid-fail",
api: "trpc",
frontend: ["solid"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "tRPC API is not supported with 'solid' frontend");
});
it("should work with tRPC + all compatible backends", async () => {
const backends = ["hono", "express", "fastify", "next", "elysia"];
for (const backend of backends) {
const config: TestConfig = {
projectName: `trpc-${backend}`,
api: "trpc",
backend: backend as Backend,
frontend: ["tanstack-router"],
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
};
// Set appropriate runtime
if (backend === "elysia") {
config.runtime = "bun";
} else {
config.runtime = "bun";
}
const result = await runTRPCTest(config);
expectSuccess(result);
}
});
});
describe("oRPC API", () => {
it("should work with oRPC + all frontends", async () => {
const frontends = [
"tanstack-router",
"react-router",
"tanstack-start",
"next",
"nuxt",
"svelte",
"solid",
"native-nativewind",
"native-unistyles",
];
for (const frontend of frontends) {
const result = await runTRPCTest({
projectName: `orpc-${frontend}`,
api: "orpc",
frontend: [frontend as Frontend],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
}
});
it("should work with oRPC + all compatible backends", async () => {
const backends = ["hono", "express", "fastify", "next", "elysia"];
for (const backend of backends) {
const config: TestConfig = {
projectName: `orpc-${backend}`,
api: "orpc",
backend: backend as Backend,
frontend: ["tanstack-router"],
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
};
// Set appropriate runtime
if (backend === "elysia") {
config.runtime = "bun";
} else {
config.runtime = "bun";
}
const result = await runTRPCTest(config);
expectSuccess(result);
}
});
});
describe("No API", () => {
it("should work with API none + basic setup", async () => {
const result = await runTRPCTest({
projectName: "no-api",
api: "none",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with API none + frontend only", async () => {
const result = await runTRPCTest({
projectName: "no-api-frontend-only",
api: "none",
frontend: ["tanstack-router"],
backend: "none",
runtime: "none",
database: "none",
orm: "none",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with API none + convex", async () => {
const result = await runTRPCTest({
projectName: "no-api-convex",
api: "none",
frontend: ["tanstack-router"],
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "clerk",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with API none + examples (non-convex backend)", async () => {
const result = await runTRPCTest({
projectName: "no-api-examples-fail",
api: "none",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(
result,
"Cannot use '--examples' when '--api' is set to 'none'",
);
});
it("should work with API none + examples + convex backend", async () => {
const result = await runTRPCTest({
projectName: "no-api-examples-convex",
api: "none",
frontend: ["tanstack-router"],
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "clerk",
addons: ["none"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
});
describe("API with Different Database Combinations", () => {
const apiDatabaseCombinations = [
{ api: "trpc", database: "sqlite", orm: "drizzle" },
{ api: "trpc", database: "postgres", orm: "drizzle" },
{ api: "trpc", database: "mysql", orm: "prisma" },
{ api: "trpc", database: "mongodb", orm: "mongoose" },
{ api: "orpc", database: "sqlite", orm: "drizzle" },
{ api: "orpc", database: "postgres", orm: "prisma" },
{ api: "orpc", database: "mysql", orm: "drizzle" },
{ api: "orpc", database: "mongodb", orm: "prisma" },
];
for (const { api, database, orm } of apiDatabaseCombinations) {
it(`should work with ${api} + ${database} + ${orm}`, async () => {
const result = await runTRPCTest({
projectName: `${api}-${database}-${orm}`,
api: api as API,
database: database as Database,
orm: orm as ORM,
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
}
});
describe("API with Authentication", () => {
it("should work with tRPC + better-auth", async () => {
const result = await runTRPCTest({
projectName: "trpc-better-auth",
api: "trpc",
auth: "better-auth",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with oRPC + better-auth", async () => {
const result = await runTRPCTest({
projectName: "orpc-better-auth",
api: "orpc",
auth: "better-auth",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with API none + convex + clerk", async () => {
const result = await runTRPCTest({
projectName: "no-api-convex-clerk",
api: "none",
auth: "clerk",
frontend: ["tanstack-router"],
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
});
describe("API with Examples", () => {
it("should work with tRPC + todo example", async () => {
const result = await runTRPCTest({
projectName: "trpc-todo",
api: "trpc",
examples: ["todo"],
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with oRPC + AI example", async () => {
const result = await runTRPCTest({
projectName: "orpc-ai",
api: "orpc",
examples: ["ai"],
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with both APIs + both examples", async () => {
const apiExampleCombinations = [
{ api: "trpc", examples: ["todo", "ai"] },
{ api: "orpc", examples: ["todo", "ai"] },
];
for (const { api, examples } of apiExampleCombinations) {
const result = await runTRPCTest({
projectName: `${api}-both-examples`,
api: api as API,
examples: examples as Examples[],
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
}
});
});
describe("All API Types", () => {
for (const api of API_TYPES) {
it(`should work with ${api} in appropriate setup`, async () => {
const config: TestConfig = {
projectName: `test-${api}`,
api,
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
};
// Set appropriate setup for each API type
if (api === "none") {
config.frontend = ["tanstack-router"];
config.backend = "none";
config.runtime = "none";
config.database = "none";
config.orm = "none";
config.auth = "none";
} else {
config.frontend = ["tanstack-router"];
config.backend = "hono";
config.runtime = "bun";
config.database = "sqlite";
config.orm = "drizzle";
config.auth = "none";
}
const result = await runTRPCTest(config);
expectSuccess(result);
});
}
});
describe("API Edge Cases", () => {
it("should handle API with complex frontend combinations", async () => {
const result = await runTRPCTest({
projectName: "api-complex-frontend",
api: "trpc",
frontend: ["tanstack-router", "native-nativewind"], // Web + Native
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should handle API with workers runtime", async () => {
const result = await runTRPCTest({
projectName: "api-workers",
api: "trpc",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "workers",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "wrangler", // Required for workers
install: false,
});
expectSuccess(result);
});
it("should handle API constraints with different runtimes", async () => {
const runtimeApiCombinations = [
{ runtime: "bun", api: "trpc" },
{ runtime: "node", api: "orpc" },
{ runtime: "workers", api: "trpc" },
];
for (const { runtime, api } of runtimeApiCombinations) {
const config: TestConfig = {
projectName: `${runtime}-${api}`,
api: api as API,
runtime: runtime as Runtime,
frontend: ["tanstack-router"],
backend: "hono",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
};
// Handle workers runtime requirements
if (runtime === "workers") {
config.serverDeploy = "wrangler";
}
const result = await runTRPCTest(config);
expectSuccess(result);
}
});
});
});

490
apps/cli/test/auth.test.ts Normal file
View File

@@ -0,0 +1,490 @@
import { describe, it } from "vitest";
import type { Backend, Database, Frontend, ORM } from "../src/types";
import {
AUTH_PROVIDERS,
expectError,
expectSuccess,
runTRPCTest,
type TestConfig,
} from "./test-utils";
describe("Authentication Configurations", () => {
describe("Better-Auth Provider", () => {
it("should work with better-auth + database", async () => {
const result = await runTRPCTest({
projectName: "better-auth-db",
auth: "better-auth",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with better-auth + different databases", async () => {
const databases = ["sqlite", "postgres", "mysql"];
for (const database of databases) {
const result = await runTRPCTest({
projectName: `better-auth-${database}`,
auth: "better-auth",
backend: "hono",
runtime: "bun",
database: database as Database,
orm: "drizzle",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
}
});
it("should work with better-auth + mongodb + mongoose", async () => {
const result = await runTRPCTest({
projectName: "better-auth-mongodb",
auth: "better-auth",
backend: "hono",
runtime: "bun",
database: "mongodb",
orm: "mongoose",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with better-auth + no database (non-convex)", async () => {
const result = await runTRPCTest({
projectName: "better-auth-no-db-fail",
auth: "better-auth",
backend: "hono",
runtime: "bun",
database: "none",
orm: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Authentication requires a database");
});
it("should fail with better-auth + convex backend", async () => {
const result = await runTRPCTest({
projectName: "better-auth-convex-fail",
auth: "better-auth",
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
api: "none",
frontend: ["tanstack-router"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Better-Auth is not compatible with Convex backend");
});
it("should work with better-auth + all compatible frontends", async () => {
const compatibleFrontends = [
"tanstack-router",
"react-router",
"tanstack-start",
"next",
"nuxt",
"svelte",
"solid",
"native-nativewind",
"native-unistyles",
];
for (const frontend of compatibleFrontends) {
const config: TestConfig = {
projectName: `better-auth-${frontend}`,
auth: "better-auth",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
frontend: [frontend as Frontend],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
};
// Handle API compatibility
if (["nuxt", "svelte", "solid"].includes(frontend)) {
config.api = "orpc";
} else {
config.api = "trpc";
}
const result = await runTRPCTest(config);
expectSuccess(result);
}
});
});
describe("Clerk Provider", () => {
it("should work with clerk + convex", async () => {
const result = await runTRPCTest({
projectName: "clerk-convex",
auth: "clerk",
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
api: "none",
frontend: ["tanstack-router"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with clerk + non-convex backend", async () => {
const result = await runTRPCTest({
projectName: "clerk-non-convex-fail",
auth: "clerk",
backend: "hono",
runtime: "bun",
database: "sqlite",
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
orm: "drizzle",
api: "trpc",
frontend: ["tanstack-router"],
expectError: true,
});
expectError(
result,
"Clerk authentication is only supported with the Convex backend",
);
});
it("should work with clerk + compatible frontends", async () => {
const compatibleFrontends = [
"tanstack-router",
"react-router",
"tanstack-start",
"next",
"native-nativewind",
"native-unistyles",
];
for (const frontend of compatibleFrontends) {
const result = await runTRPCTest({
projectName: `clerk-${frontend}`,
auth: "clerk",
backend: "convex",
runtime: "none",
database: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
dbSetup: "none",
examples: ["todo"],
orm: "none",
api: "none",
frontend: [frontend as Frontend],
install: false,
});
expectSuccess(result);
}
});
it("should fail with clerk + incompatible frontends", async () => {
const incompatibleFrontends = ["nuxt", "svelte", "solid"];
for (const frontend of incompatibleFrontends) {
const result = await runTRPCTest({
projectName: `clerk-${frontend}-fail`,
auth: "clerk",
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
api: "none",
frontend: [frontend as Frontend],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Clerk authentication is not compatible");
}
});
});
describe("No Authentication", () => {
it("should work with auth none", async () => {
const result = await runTRPCTest({
projectName: "no-auth",
auth: "none",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with auth none + no database", async () => {
const result = await runTRPCTest({
projectName: "no-auth-no-db",
auth: "none",
backend: "none",
runtime: "none",
database: "none",
orm: "none",
api: "none",
frontend: ["tanstack-router"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with auth none + convex", async () => {
const result = await runTRPCTest({
projectName: "no-auth-convex",
auth: "none",
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
api: "none",
frontend: ["tanstack-router"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
});
describe("Authentication with Different Backends", () => {
const backends = ["hono", "express", "fastify", "next", "elysia"];
for (const backend of backends) {
it(`should work with better-auth + ${backend}`, async () => {
const config: TestConfig = {
projectName: `better-auth-${backend}`,
auth: "better-auth",
backend: backend as Backend,
database: "sqlite",
orm: "drizzle",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
};
// Set appropriate runtime
if (backend === "elysia") {
config.runtime = "bun";
} else {
config.runtime = "bun";
}
const result = await runTRPCTest(config);
expectSuccess(result);
});
}
});
describe("Authentication with Different ORMs", () => {
const ormCombinations = [
{ database: "sqlite", orm: "drizzle" },
{ database: "sqlite", orm: "prisma" },
{ database: "postgres", orm: "drizzle" },
{ database: "postgres", orm: "prisma" },
{ database: "mysql", orm: "drizzle" },
{ database: "mysql", orm: "prisma" },
{ database: "mongodb", orm: "mongoose" },
{ database: "mongodb", orm: "prisma" },
];
for (const { database, orm } of ormCombinations) {
it(`should work with better-auth + ${database} + ${orm}`, async () => {
const result = await runTRPCTest({
projectName: `better-auth-${database}-${orm}`,
auth: "better-auth",
backend: "hono",
runtime: "bun",
database: database as Database,
orm: orm as ORM,
api: "trpc",
frontend: ["tanstack-router"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
}
});
describe("All Auth Providers", () => {
for (const auth of AUTH_PROVIDERS) {
it(`should work with ${auth} in appropriate setup`, async () => {
const config: TestConfig = {
projectName: `test-${auth}`,
auth,
frontend: ["tanstack-router"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
};
// Set appropriate setup for each auth provider
if (auth === "clerk") {
config.backend = "convex";
config.runtime = "none";
config.database = "none";
config.orm = "none";
config.api = "none";
} else if (auth === "better-auth") {
config.backend = "hono";
config.runtime = "bun";
config.database = "sqlite";
config.orm = "drizzle";
config.api = "trpc";
} else {
// none
config.backend = "hono";
config.runtime = "bun";
config.database = "sqlite";
config.orm = "drizzle";
config.api = "trpc";
}
const result = await runTRPCTest(config);
expectSuccess(result);
});
}
});
describe("Auth Edge Cases", () => {
it("should handle auth with complex frontend combinations", async () => {
const result = await runTRPCTest({
projectName: "auth-web-native-combo",
auth: "better-auth",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
frontend: ["tanstack-router", "native-nativewind"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should handle auth constraints with workers runtime", async () => {
const result = await runTRPCTest({
projectName: "auth-workers",
auth: "better-auth",
backend: "hono",
runtime: "workers",
database: "sqlite",
orm: "drizzle",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "wrangler",
install: false,
});
expectSuccess(result);
});
});
});

View File

@@ -0,0 +1,393 @@
import { describe, it } from "vitest";
import type { Backend, Runtime } from "../src/types";
import {
expectError,
expectSuccess,
runTRPCTest,
type TestConfig,
} from "./test-utils";
describe("Backend and Runtime Combinations", () => {
describe("Valid Backend-Runtime Combinations", () => {
const validCombinations = [
// Standard backend-runtime combinations
{ backend: "hono" as const, runtime: "bun" as const },
{ backend: "hono" as const, runtime: "node" as const },
{ backend: "hono" as const, runtime: "workers" as const },
{ backend: "express" as const, runtime: "bun" as const },
{ backend: "express" as const, runtime: "node" as const },
{ backend: "fastify" as const, runtime: "bun" as const },
{ backend: "fastify" as const, runtime: "node" as const },
{ backend: "elysia" as const, runtime: "bun" as const },
{ backend: "next" as const, runtime: "bun" as const },
{ backend: "next" as const, runtime: "node" as const },
// Special cases
{ backend: "convex" as const, runtime: "none" as const },
{ backend: "none" as const, runtime: "none" as const },
];
for (const { backend, runtime } of validCombinations) {
it(`should work with ${backend} + ${runtime}`, async () => {
const config: TestConfig = {
projectName: `${backend}-${runtime}`,
backend,
runtime,
frontend: ["tanstack-router"],
webDeploy: "none",
serverDeploy: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
};
// Set appropriate defaults based on backend
if (backend === "convex") {
config.database = "none";
config.orm = "none";
config.auth = "clerk";
config.api = "none";
} else if (backend === "none") {
config.database = "none";
config.orm = "none";
config.auth = "none";
config.api = "none";
} else {
config.database = "sqlite";
config.orm = "drizzle";
config.auth = "none";
config.api = "trpc";
}
// Set server deployment for workers runtime
if (runtime === "workers") {
config.serverDeploy = "wrangler";
}
const result = await runTRPCTest(config);
expectSuccess(result);
});
}
});
describe("Invalid Backend-Runtime Combinations", () => {
const invalidCombinations = [
// Workers runtime only works with Hono
{
backend: "express" as const,
runtime: "workers" as const,
error:
"Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend",
},
{
backend: "fastify",
runtime: "workers",
error:
"Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend",
},
{
backend: "next",
runtime: "workers",
error:
"Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend",
},
{
backend: "elysia",
runtime: "workers",
error:
"Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend",
},
// Convex backend requires runtime none
{
backend: "convex",
runtime: "bun",
error: "Convex backend requires '--runtime none'",
},
{
backend: "convex",
runtime: "node",
error: "Convex backend requires '--runtime none'",
},
{
backend: "convex",
runtime: "workers",
error: "Convex backend requires '--runtime none'",
},
// Backend none requires runtime none
{
backend: "none",
runtime: "bun",
error: "Backend 'none' requires '--runtime none'",
},
{
backend: "none",
runtime: "node",
error: "Backend 'none' requires '--runtime none'",
},
{
backend: "none",
runtime: "workers",
error: "Backend 'none' requires '--runtime none'",
},
// Runtime none only works with convex or none backend
{
backend: "hono",
runtime: "none",
error:
"'--runtime none' is only supported with '--backend convex' or '--backend none'",
},
{
backend: "express",
runtime: "none",
error:
"'--runtime none' is only supported with '--backend convex' or '--backend none'",
},
];
for (const { backend, runtime, error } of invalidCombinations) {
it(`should fail with ${backend} + ${runtime}`, async () => {
const config: TestConfig = {
projectName: `invalid-${backend}-${runtime}`,
backend: backend as Backend,
runtime: runtime as Runtime,
frontend: ["tanstack-router"],
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
};
// Set appropriate defaults based on backend
if (backend === "convex") {
config.database = "none";
config.orm = "none";
config.auth = "clerk";
config.api = "none";
} else if (backend === "none") {
config.database = "none";
config.orm = "none";
config.auth = "none";
config.api = "none";
} else {
config.database = "sqlite";
config.orm = "drizzle";
config.auth = "none";
config.api = "trpc";
}
const result = await runTRPCTest(config);
expectError(result, error);
});
}
});
describe("Convex Backend Constraints", () => {
it("should enforce all convex constraints", async () => {
const result = await runTRPCTest({
projectName: "convex-app",
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "clerk",
api: "none",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail convex with better-auth", async () => {
const result = await runTRPCTest({
projectName: "convex-better-auth",
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "better-auth",
api: "none",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Better-Auth is not compatible with Convex backend");
});
it("should fail convex with database", async () => {
const result = await runTRPCTest({
projectName: "convex-with-db",
backend: "convex",
runtime: "none",
database: "postgres",
orm: "drizzle",
auth: "clerk",
api: "none",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Convex backend requires '--database none'");
});
});
describe("Workers Runtime Constraints", () => {
it("should work with workers + hono + compatible database", async () => {
const result = await runTRPCTest({
projectName: "workers-compatible",
backend: "hono",
runtime: "workers",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "wrangler", // Workers requires server deployment
install: false,
});
expectSuccess(result);
});
it("should fail workers with mongodb", async () => {
const result = await runTRPCTest({
projectName: "workers-mongodb",
backend: "hono",
runtime: "workers",
database: "mongodb",
orm: "prisma",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(
result,
"Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database",
);
});
it("should fail workers without server deployment", async () => {
const result = await runTRPCTest({
projectName: "workers-no-deploy",
backend: "hono",
runtime: "workers",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(
result,
"Cloudflare Workers runtime requires a server deployment",
);
});
});
describe("All Backend Types", () => {
const backends = [
"hono",
"express",
"fastify",
"next",
"elysia",
"convex",
"none",
] as const;
for (const backend of backends) {
it(`should work with appropriate defaults for ${backend}`, async () => {
const config: TestConfig = {
projectName: `test-${backend}`,
backend: backend as Backend,
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
};
// Set appropriate defaults for each backend
switch (backend) {
case "convex":
config.runtime = "none";
config.database = "none";
config.orm = "none";
config.auth = "clerk";
config.api = "none";
break;
case "none":
config.runtime = "none";
config.database = "none";
config.orm = "none";
config.auth = "none";
config.api = "none";
break;
case "elysia":
config.runtime = "bun";
config.database = "sqlite";
config.orm = "drizzle";
config.auth = "none";
config.api = "trpc";
break;
default:
config.runtime = "bun";
config.database = "sqlite";
config.orm = "drizzle";
config.auth = "none";
config.api = "trpc";
}
const result = await runTRPCTest(config);
expectSuccess(result);
});
}
});
});

View File

@@ -0,0 +1,176 @@
import { describe, expect, it } from "vitest";
import {
expectError,
expectSuccess,
PACKAGE_MANAGERS,
runTRPCTest,
} from "./test-utils";
describe("Basic Configurations", () => {
describe("Default Configuration", () => {
it("should create project with --yes flag (default config)", async () => {
const result = await runTRPCTest({
projectName: "default-app",
yes: true,
install: false,
});
expectSuccess(result);
expect(result.result?.projectConfig.projectName).toBe("default-app");
});
it("should create project with explicit default values", async () => {
const result = await runTRPCTest({
projectName: "explicit-defaults",
database: "sqlite",
orm: "drizzle",
backend: "hono",
runtime: "bun",
frontend: ["tanstack-router"],
auth: "better-auth",
api: "trpc",
addons: ["turborepo"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false, // Skip installation for faster tests
});
expectSuccess(result);
expect(result.result?.projectConfig.projectName).toBe(
"explicit-defaults",
);
});
});
describe("Package Managers", () => {
for (const packageManager of PACKAGE_MANAGERS) {
it(`should work with ${packageManager}`, async () => {
const result = await runTRPCTest({
projectName: `${packageManager}-app`,
packageManager,
yes: true,
install: false,
});
expectSuccess(result);
expect(result.result?.projectConfig.packageManager).toBe(
packageManager,
);
});
}
});
describe("Git Options", () => {
it("should work with git enabled", async () => {
const result = await runTRPCTest({
projectName: "git-enabled",
yes: true,
git: true,
install: false,
});
expectSuccess(result);
expect(result.result?.projectConfig.git).toBe(true);
});
it("should work with git disabled", async () => {
const result = await runTRPCTest({
projectName: "git-disabled",
yes: true,
git: false,
install: false,
});
expectSuccess(result);
expect(result.result?.projectConfig.git).toBe(false);
});
});
describe("Installation Options", () => {
it("should work with install enabled", async () => {
const result = await runTRPCTest({
projectName: "install-enabled",
yes: true,
install: true,
});
expectSuccess(result);
expect(result.result?.projectConfig.install).toBe(true);
});
it("should work with install disabled", async () => {
const result = await runTRPCTest({
projectName: "install-disabled",
yes: true,
install: false,
});
expectSuccess(result);
expect(result.result?.projectConfig.install).toBe(false);
});
});
describe("YOLO Mode", () => {
it("should bypass validations with --yolo flag", async () => {
// This would normally fail validation but should pass with yolo
const result = await runTRPCTest({
projectName: "yolo-app",
yolo: true,
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
api: "trpc",
database: "mongodb",
orm: "drizzle", // Incompatible combination
auth: "better-auth",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
expect(result.result?.projectConfig.projectName).toBe("yolo-app");
});
});
describe("Error Handling", () => {
it("should fail with invalid project name", async () => {
const result = await runTRPCTest({
projectName: "<invalid>",
expectError: true,
});
expectError(result, "invalid characters");
});
it("should fail when combining --yes with configuration flags", async () => {
const result = await runTRPCTest({
projectName: "yes-with-flags",
yes: true, // Explicitly set yes flag
database: "postgres",
orm: "drizzle",
backend: "hono",
runtime: "bun",
frontend: ["tanstack-router"],
auth: "better-auth",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(
result,
"Cannot combine --yes with core stack configuration flags",
);
});
});
});

View File

@@ -0,0 +1,524 @@
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import {
cleanupSmokeDirectory,
expectSuccess,
runTRPCTest,
type TestConfig,
} from "./test-utils";
describe("CLI Performance Benchmarks", () => {
beforeAll(async () => {
await cleanupSmokeDirectory();
});
afterAll(async () => {
await cleanupSmokeDirectory();
});
describe("Basic Project Creation Benchmarks", () => {
it("should benchmark default configuration creation", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-default",
yes: true,
install: false, // Skip install for faster benchmarking
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(10000); // Should complete within 10 seconds
console.log(`✅ Default configuration: ${duration.toFixed(2)}ms`);
});
it("should benchmark minimal configuration creation", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-minimal",
frontend: ["none"],
backend: "none",
runtime: "none",
database: "none",
orm: "none",
auth: "none",
api: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
serverDeploy: "none",
webDeploy: "none",
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(8000);
console.log(`✅ Minimal configuration: ${duration.toFixed(2)}ms`);
});
it("should benchmark full-stack configuration creation", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-fullstack",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "postgres",
orm: "drizzle",
auth: "better-auth",
api: "trpc",
addons: ["turborepo", "biome"],
examples: ["todo"],
dbSetup: "neon",
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(15000); // Should complete within 15 seconds
console.log(`✅ Full-stack configuration: ${duration.toFixed(2)}ms`);
});
});
describe("Database Setup Benchmarks", () => {
it("should benchmark SQLite with Drizzle setup", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-sqlite-drizzle",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(12000);
console.log(`✅ SQLite + Drizzle: ${duration.toFixed(2)}ms`);
});
it("should benchmark PostgreSQL with Prisma setup", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-postgres-prisma",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "postgres",
orm: "prisma",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(12000);
console.log(`✅ PostgreSQL + Prisma: ${duration.toFixed(2)}ms`);
});
it("should benchmark MongoDB with Mongoose setup", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-mongodb-mongoose",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "mongodb",
orm: "mongoose",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(12000);
console.log(`✅ MongoDB + Mongoose: ${duration.toFixed(2)}ms`);
});
});
describe("Frontend Framework Benchmarks", () => {
it("should benchmark TanStack Router setup", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-tanstack-router",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(12000);
console.log(`✅ TanStack Router: ${duration.toFixed(2)}ms`);
});
it("should benchmark Next.js setup", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-nextjs",
frontend: ["next"],
backend: "next",
runtime: "node",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(12000);
console.log(`✅ Next.js: ${duration.toFixed(2)}ms`);
});
it("should benchmark Nuxt setup", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-nuxt",
frontend: ["nuxt"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "orpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(12000);
console.log(`✅ Nuxt: ${duration.toFixed(2)}ms`);
});
});
describe("Backend Framework Benchmarks", () => {
it("should benchmark Hono setup", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-hono",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(12000);
console.log(`✅ Hono: ${duration.toFixed(2)}ms`);
});
it("should benchmark Express setup", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-express",
frontend: ["tanstack-router"],
backend: "express",
runtime: "node",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(12000);
console.log(`✅ Express: ${duration.toFixed(2)}ms`);
});
it("should benchmark Convex setup", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-convex",
frontend: ["tanstack-router"],
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "none",
api: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(12000);
console.log(`✅ Convex: ${duration.toFixed(2)}ms`);
});
});
describe("Addon Benchmarks", () => {
it("should benchmark Turborepo addon", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-turborepo",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["turborepo"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(12000);
console.log(`✅ Turborepo addon: ${duration.toFixed(2)}ms`);
});
it("should benchmark Biome addon", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-biome",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["biome"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(12000);
console.log(`✅ Biome addon: ${duration.toFixed(2)}ms`);
});
it("should benchmark multiple addons", async () => {
const startTime = performance.now();
const result = await runTRPCTest({
projectName: "benchmark-multiple-addons",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["turborepo", "biome", "husky"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(15000);
console.log(`✅ Multiple addons: ${duration.toFixed(2)}ms`);
});
});
describe("Performance Regression Tests", () => {
it("should not exceed performance thresholds", async () => {
const configurations = [
{
name: "Minimal",
config: {
projectName: "perf-minimal",
frontend: ["none"],
backend: "none",
runtime: "none",
database: "none",
orm: "none",
auth: "none",
api: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
},
threshold: 5000, // 5 seconds
},
{
name: "Default",
config: {
projectName: "perf-default",
yes: true,
install: false,
},
threshold: 8000, // 8 seconds
},
{
name: "Complex",
config: {
projectName: "perf-complex",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "postgres",
orm: "prisma",
auth: "better-auth",
api: "trpc",
addons: ["turborepo", "biome"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
},
threshold: 12000, // 12 seconds
},
];
for (const { name, config, threshold } of configurations) {
const startTime = performance.now();
const result = await runTRPCTest(config as TestConfig);
const endTime = performance.now();
const duration = endTime - startTime;
expectSuccess(result);
expect(duration).toBeLessThan(threshold);
console.log(
`${name} performance: ${duration.toFixed(2)}ms (threshold: ${threshold}ms)`,
);
}
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,237 @@
import { describe, it } from "vitest";
import type { Database, ORM } from "../src/types";
import {
DATABASES,
expectError,
expectSuccess,
runTRPCTest,
} from "./test-utils";
describe("Database and ORM Combinations", () => {
describe("Valid Database-ORM Combinations", () => {
const validCombinations: Array<{ database: Database; orm: ORM }> = [
// SQLite combinations
{ database: "sqlite" as Database, orm: "drizzle" as ORM },
{ database: "sqlite" as Database, orm: "prisma" as ORM },
// PostgreSQL combinations
{ database: "postgres" as Database, orm: "drizzle" as ORM },
{ database: "postgres" as Database, orm: "prisma" as ORM },
// MySQL combinations
{ database: "mysql" as Database, orm: "drizzle" as ORM },
{ database: "mysql" as Database, orm: "prisma" as ORM },
// MongoDB combinations
{ database: "mongodb" as Database, orm: "mongoose" as ORM },
{ database: "mongodb" as Database, orm: "prisma" as ORM },
// None combinations
{ database: "none" as Database, orm: "none" as ORM },
];
for (const { database, orm } of validCombinations) {
it(`should work with ${database} + ${orm}`, async () => {
const result = await runTRPCTest({
projectName: `${database}-${orm}`,
database,
orm,
backend: "hono",
runtime: "bun",
frontend: ["tanstack-router"],
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
}
});
describe("Invalid Database-ORM Combinations", () => {
const invalidCombinations: Array<{
database: Database;
orm: ORM;
error: string;
}> = [
// MongoDB with Drizzle (not supported)
{
database: "mongodb" as Database,
orm: "drizzle" as ORM,
error: "Drizzle ORM does not support MongoDB",
},
// Mongoose with non-MongoDB
{
database: "sqlite" as Database,
orm: "mongoose" as ORM,
error: "Mongoose ORM requires MongoDB database",
},
{
database: "postgres" as Database,
orm: "mongoose" as ORM,
error: "Mongoose ORM requires MongoDB database",
},
{
database: "mysql" as Database,
orm: "mongoose" as ORM,
error: "Mongoose ORM requires MongoDB database",
},
// Database without ORM
{
database: "sqlite" as Database,
orm: "none" as ORM,
error: "Database selection requires an ORM",
},
{
database: "postgres" as Database,
orm: "none" as ORM,
error: "Database selection requires an ORM",
},
// ORM without database
{
database: "none" as Database,
orm: "drizzle" as ORM,
error: "ORM selection requires a database",
},
{
database: "none" as Database,
orm: "prisma" as ORM,
error: "ORM selection requires a database",
},
];
for (const { database, orm, error } of invalidCombinations) {
it(`should fail with ${database} + ${orm}`, async () => {
const result = await runTRPCTest({
projectName: `invalid-${database}-${orm}`,
database,
orm,
backend: "hono",
runtime: "bun",
frontend: ["tanstack-router"],
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, error);
});
}
});
describe("Database-ORM with Authentication", () => {
it("should work with database + auth", async () => {
const result = await runTRPCTest({
projectName: "db-auth",
database: "sqlite",
orm: "drizzle",
auth: "better-auth",
backend: "hono",
runtime: "bun",
frontend: ["tanstack-router"],
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with auth but no database (non-convex backend)", async () => {
const result = await runTRPCTest({
projectName: "auth-no-db",
database: "none",
orm: "none",
auth: "better-auth",
backend: "hono",
runtime: "bun",
frontend: ["tanstack-router"],
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Authentication requires a database");
});
it("should work with auth but no database (convex backend)", async () => {
const result = await runTRPCTest({
projectName: "convex-auth-no-db",
database: "none",
orm: "none",
auth: "none",
backend: "convex",
runtime: "none",
frontend: ["tanstack-router"],
api: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
});
describe("All Database Types", () => {
for (const database of DATABASES) {
if (database === "none") continue;
it(`should have valid ORM options for ${database}`, async () => {
// Test with the most compatible ORM for each database
const ormMap = {
sqlite: "drizzle",
postgres: "drizzle",
mysql: "drizzle",
mongodb: "mongoose",
};
const orm = ormMap[database as keyof typeof ormMap];
const result = await runTRPCTest({
projectName: `test-${database}`,
database: database as Database,
orm: orm as ORM,
backend: "hono",
runtime: "bun",
frontend: ["tanstack-router"],
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
}
});
});

View File

@@ -0,0 +1,506 @@
import { describe, it } from "vitest";
import {
DB_SETUPS,
expectError,
expectSuccess,
runTRPCTest,
type TestConfig,
} from "./test-utils";
describe("Database Setup Configurations", () => {
describe("SQLite Database Setups", () => {
it("should work with Turso + SQLite", async () => {
const result = await runTRPCTest({
projectName: "turso-sqlite",
database: "sqlite",
orm: "drizzle",
dbSetup: "turso",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
install: false,
});
expectSuccess(result);
});
it("should work with D1 + SQLite + Workers", async () => {
const result = await runTRPCTest({
projectName: "d1-sqlite-workers",
database: "sqlite",
orm: "drizzle",
dbSetup: "d1",
backend: "hono",
runtime: "workers",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "wrangler",
manualDb: true,
install: false,
});
expectSuccess(result);
});
it("should fail with Turso + non-SQLite database", async () => {
const result = await runTRPCTest({
projectName: "turso-postgres-fail",
database: "postgres",
orm: "drizzle",
dbSetup: "turso",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
expectError: true,
});
expectError(result, "Turso setup requires SQLite database");
});
});
describe("PostgreSQL Database Setups", () => {
it("should work with Neon + PostgreSQL", async () => {
const result = await runTRPCTest({
projectName: "neon-postgres",
database: "postgres",
orm: "drizzle",
dbSetup: "neon",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
install: false,
});
expectSuccess(result);
});
it("should work with Supabase + PostgreSQL", async () => {
const result = await runTRPCTest({
projectName: "supabase-postgres",
database: "postgres",
orm: "drizzle",
dbSetup: "supabase",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
install: false,
});
expectSuccess(result);
});
it("should work with Prisma PostgreSQL setup", async () => {
const result = await runTRPCTest({
projectName: "prisma-postgres-setup",
database: "postgres",
orm: "prisma",
dbSetup: "prisma-postgres",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
install: false,
});
expectSuccess(result);
});
it("should fail with Neon + non-PostgreSQL database", async () => {
const result = await runTRPCTest({
projectName: "neon-mysql-fail",
database: "mysql",
orm: "drizzle",
dbSetup: "neon",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
expectError: true,
});
expectError(result, "Neon setup requires PostgreSQL database");
});
});
describe("MySQL Database Setups", () => {
it("should work with PlanetScale + MySQL", async () => {
const result = await runTRPCTest({
projectName: "planetscale-mysql",
database: "mysql",
orm: "drizzle",
dbSetup: "planetscale",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
install: false,
});
expectSuccess(result);
});
it("should work with PlanetScale + PostgreSQL", async () => {
const result = await runTRPCTest({
projectName: "planetscale-postgres",
database: "postgres",
orm: "drizzle",
dbSetup: "planetscale",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
install: false,
});
expectSuccess(result);
});
});
describe("MongoDB Database Setups", () => {
it("should work with MongoDB Atlas + MongoDB", async () => {
const result = await runTRPCTest({
projectName: "mongodb-atlas",
database: "mongodb",
orm: "mongoose",
dbSetup: "mongodb-atlas",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
install: false,
});
expectSuccess(result);
});
it("should fail with MongoDB Atlas + non-MongoDB database", async () => {
const result = await runTRPCTest({
projectName: "mongodb-atlas-sqlite-fail",
database: "sqlite",
orm: "drizzle",
dbSetup: "mongodb-atlas",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
expectError: true,
});
expectError(result, "MongoDB Atlas setup requires MongoDB database");
});
});
describe("Docker Database Setup", () => {
it("should work with Docker + PostgreSQL", async () => {
const result = await runTRPCTest({
projectName: "docker-postgres",
database: "postgres",
orm: "drizzle",
dbSetup: "docker",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
install: false,
});
expectSuccess(result);
});
it("should work with Docker + MySQL", async () => {
const result = await runTRPCTest({
projectName: "docker-mysql",
database: "mysql",
orm: "drizzle",
dbSetup: "docker",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
install: false,
});
expectSuccess(result);
});
it("should work with Docker + MongoDB", async () => {
const result = await runTRPCTest({
projectName: "docker-mongodb",
database: "mongodb",
orm: "mongoose",
dbSetup: "docker",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
install: false,
});
expectSuccess(result);
});
it("should fail with Docker + SQLite", async () => {
const result = await runTRPCTest({
projectName: "docker-sqlite-fail",
database: "sqlite",
orm: "drizzle",
dbSetup: "docker",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
expectError: true,
});
expectError(
result,
"Docker setup is not compatible with SQLite database",
);
});
});
describe("No Database Setup", () => {
it("should work with dbSetup none", async () => {
const result = await runTRPCTest({
projectName: "no-db-setup",
database: "sqlite",
orm: "drizzle",
dbSetup: "none",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with dbSetup but no database", async () => {
const result = await runTRPCTest({
projectName: "db-setup-no-db-fail",
database: "none",
orm: "none",
dbSetup: "turso",
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(
result,
"Database setup requires a database. Please choose a database or set '--db-setup none'.",
);
});
});
describe("Special Runtime Constraints", () => {
it("should work with D1 + Workers runtime", async () => {
const result = await runTRPCTest({
projectName: "d1-workers-valid",
database: "sqlite",
orm: "drizzle",
dbSetup: "none",
backend: "hono",
runtime: "workers",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "wrangler",
install: false,
});
expectSuccess(result);
});
it("should fail with D1 + non-Workers runtime", async () => {
const result = await runTRPCTest({
projectName: "d1-node-fail",
database: "sqlite",
orm: "drizzle",
dbSetup: "d1",
backend: "hono",
runtime: "node",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(
result,
"Cloudflare D1 setup requires SQLite database and Cloudflare Workers runtime",
);
});
});
describe("All Database Setup Types", () => {
for (const dbSetup of DB_SETUPS) {
if (dbSetup === "none") continue;
it(`should work with ${dbSetup} in appropriate setup`, async () => {
const config: TestConfig = {
projectName: `test-${dbSetup}`,
dbSetup,
backend: "hono",
runtime: "bun",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
webDeploy: "none",
serverDeploy: "none",
manualDb: true,
install: false,
};
// Set appropriate database and ORM for each setup
switch (dbSetup) {
case "turso":
config.database = "sqlite";
config.orm = "drizzle";
break;
case "neon":
case "supabase":
case "prisma-postgres":
config.database = "postgres";
config.orm = "drizzle";
break;
case "planetscale":
config.database = "mysql";
config.orm = "drizzle";
break;
case "mongodb-atlas":
config.database = "mongodb";
config.orm = "mongoose";
break;
case "d1":
config.database = "sqlite";
config.orm = "drizzle";
config.runtime = "workers";
config.serverDeploy = "wrangler";
break;
case "docker":
config.database = "postgres";
config.orm = "drizzle";
break;
}
const result = await runTRPCTest(config);
expectSuccess(result);
});
}
});
});

View File

@@ -0,0 +1,560 @@
import { describe, it } from "vitest";
import {
expectError,
expectSuccess,
runTRPCTest,
SERVER_DEPLOYS,
type TestConfig,
WEB_DEPLOYS,
} from "./test-utils";
describe("Deployment Configurations", () => {
describe("Web Deployment", () => {
describe("Valid Web Deploy Configurations", () => {
for (const webDeploy of WEB_DEPLOYS) {
if (webDeploy === "none") continue;
it(`should work with ${webDeploy} web deploy + web frontend`, async () => {
const result = await runTRPCTest({
projectName: `${webDeploy}-web-deploy`,
webDeploy: webDeploy,
serverDeploy: "none",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
});
expectSuccess(result);
});
}
});
it("should work with web deploy none", async () => {
const result = await runTRPCTest({
projectName: "no-web-deploy",
webDeploy: "none",
serverDeploy: "none",
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with web deploy but no web frontend", async () => {
const result = await runTRPCTest({
projectName: "web-deploy-no-web-frontend-fail",
webDeploy: "wrangler",
serverDeploy: "none",
frontend: ["native-nativewind"], // Native frontend only
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
expectError: true,
});
expectError(result, "'--web-deploy' requires a web frontend");
});
it("should work with web deploy + mixed web and native frontends", async () => {
const result = await runTRPCTest({
projectName: "web-deploy-mixed-frontends",
webDeploy: "wrangler",
serverDeploy: "none",
frontend: ["tanstack-router", "native-nativewind"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
});
expectSuccess(result);
});
it("should work with web deploy + all web frontends", async () => {
const webFrontends = [
"tanstack-router",
"react-router",
"tanstack-start",
"next",
"nuxt",
"svelte",
"solid",
] as const;
for (const frontend of webFrontends) {
const config: TestConfig = {
projectName: `web-deploy-${frontend}`,
webDeploy: "wrangler",
serverDeploy: "none",
frontend: [frontend],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
};
// Handle API compatibility
if (["nuxt", "svelte", "solid"].includes(frontend)) {
config.api = "orpc";
} else {
config.api = "trpc";
}
const result = await runTRPCTest(config);
expectSuccess(result);
}
});
});
describe("Server Deployment", () => {
describe("Valid Server Deploy Configurations", () => {
for (const serverDeploy of SERVER_DEPLOYS) {
if (serverDeploy === "none") continue;
it(`should work with ${serverDeploy} server deploy + backend`, async () => {
const result = await runTRPCTest({
projectName: `${serverDeploy}-server-deploy`,
webDeploy: "none",
serverDeploy: serverDeploy,
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
});
expectSuccess(result);
});
}
});
it("should work with server deploy none", async () => {
const result = await runTRPCTest({
projectName: "no-server-deploy",
webDeploy: "none",
serverDeploy: "none",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with server deploy but no backend", async () => {
const result = await runTRPCTest({
projectName: "server-deploy-no-backend-fail",
webDeploy: "none",
serverDeploy: "wrangler",
backend: "none",
runtime: "none",
database: "none",
orm: "none",
auth: "none",
api: "none",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
expectError: true,
});
expectError(
result,
"Backend 'none' requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.",
);
});
it("should work with server deploy + all compatible backends", async () => {
const backends = [
"hono",
"express",
"fastify",
"next",
"elysia",
] as const;
for (const backend of backends) {
const config: TestConfig = {
projectName: `server-deploy-${backend}`,
webDeploy: "none",
serverDeploy: "wrangler",
backend,
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
};
// Set appropriate runtime
if (backend === "elysia") {
config.runtime = "bun";
} else {
config.runtime = "bun";
}
const result = await runTRPCTest(config);
expectSuccess(result);
}
});
it("should fail with server deploy + convex backend", async () => {
const result = await runTRPCTest({
projectName: "server-deploy-convex-fail",
webDeploy: "none",
serverDeploy: "wrangler",
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "clerk",
api: "none",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
expectError: true,
});
expectError(result, "Convex backend requires '--server-deploy none'");
});
});
describe("Workers Runtime Deployment Constraints", () => {
it("should work with workers runtime + server deploy", async () => {
const result = await runTRPCTest({
projectName: "workers-server-deploy",
webDeploy: "none",
runtime: "workers",
serverDeploy: "wrangler",
backend: "hono",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "d1",
install: false,
});
expectSuccess(result);
});
it("should fail with workers runtime + no server deploy", async () => {
const result = await runTRPCTest({
projectName: "workers-no-server-deploy-fail",
runtime: "workers",
serverDeploy: "none",
backend: "hono",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
expectError: true,
});
expectError(
result,
"Cloudflare Workers runtime requires a server deployment",
);
});
});
describe("Combined Web and Server Deployment", () => {
it("should work with both web and server deploy", async () => {
const result = await runTRPCTest({
projectName: "web-server-deploy-combo",
webDeploy: "wrangler",
serverDeploy: "wrangler",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
});
expectSuccess(result);
});
it("should work with different deploy providers", async () => {
const result = await runTRPCTest({
projectName: "different-deploy-providers",
webDeploy: "wrangler",
serverDeploy: "alchemy",
backend: "hono",
runtime: "workers",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
});
expectSuccess(result);
});
it("should work with web deploy only", async () => {
const result = await runTRPCTest({
projectName: "web-deploy-only",
webDeploy: "wrangler",
serverDeploy: "none",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
});
expectSuccess(result);
});
it("should work with server deploy only", async () => {
const result = await runTRPCTest({
projectName: "server-deploy-only",
webDeploy: "none",
serverDeploy: "wrangler",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
});
expectSuccess(result);
});
});
describe("Deployment with Special Backend Constraints", () => {
it("should work with deployment + next backend", async () => {
const result = await runTRPCTest({
projectName: "deploy-next-backend",
webDeploy: "wrangler",
serverDeploy: "wrangler",
backend: "next",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["next"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
});
expectSuccess(result);
});
it("should work with deployment + fullstack setup", async () => {
const result = await runTRPCTest({
projectName: "deploy-fullstack",
webDeploy: "wrangler",
serverDeploy: "wrangler",
backend: "hono",
runtime: "workers",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
});
expectSuccess(result);
});
});
describe("All Deployment Options", () => {
const deployOptions: ReadonlyArray<{
webDeploy: TestConfig["webDeploy"];
serverDeploy: TestConfig["serverDeploy"];
}> = [
{ webDeploy: "wrangler", serverDeploy: "wrangler" },
{ webDeploy: "wrangler", serverDeploy: "alchemy" },
{ webDeploy: "alchemy", serverDeploy: "wrangler" },
{ webDeploy: "alchemy", serverDeploy: "alchemy" },
{ webDeploy: "wrangler", serverDeploy: "none" },
{ webDeploy: "alchemy", serverDeploy: "none" },
{ webDeploy: "none", serverDeploy: "wrangler" },
{ webDeploy: "none", serverDeploy: "alchemy" },
{ webDeploy: "none", serverDeploy: "none" },
];
for (const { webDeploy, serverDeploy } of deployOptions) {
it(`should work with webDeploy: ${webDeploy}, serverDeploy: ${serverDeploy}`, async () => {
const config: TestConfig = {
projectName: `deploy-${webDeploy}-${serverDeploy}`,
webDeploy,
serverDeploy,
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
install: false,
};
// Handle special cases
if (
webDeploy !== "none" &&
!config.frontend?.some((f) =>
[
"tanstack-router",
"react-router",
"tanstack-start",
"next",
"nuxt",
"svelte",
"solid",
].includes(f),
)
) {
config.frontend = ["tanstack-router"]; // Ensure web frontend for web deploy
}
if (serverDeploy !== "none" && config.backend === "none") {
config.backend = "hono"; // Ensure backend for server deploy
}
const result = await runTRPCTest(config);
expectSuccess(result);
});
}
});
describe("Deployment Edge Cases", () => {
it("should handle deployment with complex configurations", async () => {
const result = await runTRPCTest({
projectName: "complex-deployment",
webDeploy: "wrangler",
serverDeploy: "wrangler",
backend: "hono",
runtime: "workers",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"], // Single web frontend (compatible with PWA)
addons: ["pwa", "turborepo"],
examples: ["todo"],
install: false,
});
expectSuccess(result);
});
it("should handle deployment constraints properly", async () => {
// This should fail because we have web deploy but only native frontend
const result = await runTRPCTest({
projectName: "deployment-constraints-fail",
webDeploy: "wrangler",
serverDeploy: "none",
backend: "none", // No backend but we have server deploy
runtime: "none",
database: "none",
orm: "none",
auth: "none",
api: "none",
frontend: ["native-nativewind"], // Only native frontend
addons: ["none"],
examples: ["none"],
dbSetup: "none",
expectError: true,
});
expectError(result, "'--web-deploy' requires a web frontend");
});
});
});

View File

@@ -0,0 +1,444 @@
import { describe, it } from "vitest";
import {
EXAMPLES,
expectError,
expectSuccess,
runTRPCTest,
type TestConfig,
} from "./test-utils";
describe("Example Configurations", () => {
describe("Todo Example", () => {
it("should work with todo example + database + backend", async () => {
const result = await runTRPCTest({
projectName: "todo-with-db",
examples: ["todo"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with todo example + convex backend", async () => {
const result = await runTRPCTest({
projectName: "todo-convex",
examples: ["todo"],
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "clerk",
api: "none",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with todo example + no backend", async () => {
const result = await runTRPCTest({
projectName: "todo-no-backend",
examples: ["todo"],
backend: "none",
runtime: "none",
database: "none",
orm: "none",
auth: "none",
api: "none",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with todo example + backend + no database", async () => {
const result = await runTRPCTest({
projectName: "todo-backend-no-db-fail",
examples: ["todo"],
backend: "hono",
runtime: "bun",
database: "none",
orm: "none",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(
result,
"The 'todo' example requires a database if a backend (other than Convex) is present",
);
});
});
describe("AI Example", () => {
it("should work with AI example + React frontend", async () => {
const result = await runTRPCTest({
projectName: "ai-react",
examples: ["ai"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with AI example + Next.js", async () => {
const result = await runTRPCTest({
projectName: "ai-next",
examples: ["ai"],
backend: "next",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["next"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with AI example + Nuxt", async () => {
const result = await runTRPCTest({
projectName: "ai-nuxt",
examples: ["ai"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "orpc", // tRPC not supported with Nuxt
frontend: ["nuxt"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should work with AI example + Svelte", async () => {
const result = await runTRPCTest({
projectName: "ai-svelte",
examples: ["ai"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "orpc", // tRPC not supported with Svelte
frontend: ["svelte"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with AI example + Solid frontend", async () => {
const result = await runTRPCTest({
projectName: "ai-solid-fail",
examples: ["ai"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "orpc",
frontend: ["solid"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(
result,
"The 'ai' example is not compatible with the Solid frontend",
);
});
it("should work with AI example + Convex", async () => {
const result = await runTRPCTest({
projectName: "ai-convex",
examples: ["ai"],
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "clerk",
api: "none",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
});
describe("Multiple Examples", () => {
it("should work with both todo and AI examples", async () => {
const result = await runTRPCTest({
projectName: "todo-ai-combo",
examples: ["todo", "ai"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with both examples if one is incompatible", async () => {
const result = await runTRPCTest({
projectName: "todo-ai-solid-fail",
examples: ["todo", "ai"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "orpc",
frontend: ["solid"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(
result,
"The 'ai' example is not compatible with the Solid frontend",
);
});
});
describe("Examples with None Option", () => {
it("should work with examples none", async () => {
const result = await runTRPCTest({
projectName: "no-examples",
examples: ["none"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with none + other examples", async () => {
const result = await runTRPCTest({
projectName: "none-with-examples-fail",
examples: ["none", "todo"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Cannot combine 'none' with other examples");
});
});
describe("Examples with API None", () => {
it("should fail with examples when API is none (non-convex backend)", async () => {
const result = await runTRPCTest({
projectName: "examples-api-none-fail",
examples: ["todo"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "none",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(
result,
"Cannot use '--examples' when '--api' is set to 'none'",
);
});
it("should work with examples when API is none (convex backend)", async () => {
const result = await runTRPCTest({
projectName: "examples-api-none-convex",
examples: ["todo"],
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "clerk",
api: "none",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
});
describe("All Example Types", () => {
for (const example of EXAMPLES) {
if (example === "none") continue;
it(`should work with ${example} example in appropriate setup`, async () => {
const config: TestConfig = {
projectName: `test-${example}`,
examples: [example],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
};
const result = await runTRPCTest(config);
expectSuccess(result);
});
}
});
describe("Example Edge Cases", () => {
it("should work with empty examples array", async () => {
const result = await runTRPCTest({
projectName: "empty-examples",
examples: ["none"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should handle complex example constraints", async () => {
// Todo example with backend but no database should fail
const result = await runTRPCTest({
projectName: "complex-example-constraints",
examples: ["todo"],
backend: "express", // Non-convex backend
runtime: "bun",
database: "none", // No database
orm: "none",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(
result,
"The 'todo' example requires a database if a backend (other than Convex) is present",
);
});
});
});

View File

@@ -0,0 +1,454 @@
import { describe, it } from "vitest";
import {
expectError,
expectSuccess,
runTRPCTest,
type TestConfig,
} from "./test-utils";
describe("Frontend Configurations", () => {
describe("Single Frontend Options", () => {
const singleFrontends = [
"tanstack-router",
"react-router",
"tanstack-start",
"next",
"nuxt",
"native-nativewind",
"native-unistyles",
"svelte",
"solid",
] satisfies ReadonlyArray<
| "tanstack-router"
| "react-router"
| "tanstack-start"
| "next"
| "nuxt"
| "native-nativewind"
| "native-unistyles"
| "svelte"
| "solid"
>;
for (const frontend of singleFrontends) {
it(`should work with ${frontend}`, async () => {
const config: TestConfig = {
projectName: `${frontend}-app`,
frontend: [frontend],
install: false,
};
// Set compatible defaults based on frontend
if (frontend === "solid") {
// Solid is not compatible with Convex backend
config.backend = "hono";
config.runtime = "bun";
config.database = "sqlite";
config.orm = "drizzle";
config.auth = "none";
config.api = "orpc"; // tRPC not supported with solid
config.addons = ["none"];
config.examples = ["none"];
config.dbSetup = "none";
config.webDeploy = "none";
config.serverDeploy = "none";
} else if (["nuxt", "svelte"].includes(frontend)) {
config.backend = "hono";
config.runtime = "bun";
config.database = "sqlite";
config.orm = "drizzle";
config.auth = "none";
config.api = "orpc"; // tRPC not supported with nuxt/svelte
config.addons = ["none"];
config.examples = ["none"];
config.dbSetup = "none";
config.webDeploy = "none";
config.serverDeploy = "none";
} else {
config.backend = "hono";
config.runtime = "bun";
config.database = "sqlite";
config.orm = "drizzle";
config.auth = "none";
config.api = "trpc";
config.addons = ["none"];
config.examples = ["none"];
config.dbSetup = "none";
config.webDeploy = "none";
config.serverDeploy = "none";
}
const result = await runTRPCTest(config);
expectSuccess(result);
});
}
});
describe("Frontend Compatibility with API", () => {
it("should work with React frontends + tRPC", async () => {
const result = await runTRPCTest({
projectName: "react-trpc",
frontend: ["tanstack-router"],
api: "trpc",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with Nuxt + tRPC", async () => {
const result = await runTRPCTest({
projectName: "nuxt-trpc-fail",
frontend: ["nuxt"],
api: "trpc",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "tRPC API is not supported with 'nuxt' frontend");
});
it("should fail with Svelte + tRPC", async () => {
const result = await runTRPCTest({
projectName: "svelte-trpc-fail",
frontend: ["svelte"],
api: "trpc",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "tRPC API is not supported with 'svelte' frontend");
});
it("should fail with Solid + tRPC", async () => {
const result = await runTRPCTest({
projectName: "solid-trpc-fail",
frontend: ["solid"],
api: "trpc",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "tRPC API is not supported with 'solid' frontend");
});
it("should work with non-React frontends + oRPC", async () => {
const frontends = ["nuxt", "svelte", "solid"] as const;
for (const frontend of frontends) {
const result = await runTRPCTest({
projectName: `${frontend}-orpc`,
frontend: [frontend],
api: "orpc",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
}
});
});
describe("Frontend Compatibility with Backend", () => {
it("should fail Solid + Convex", async () => {
const result = await runTRPCTest({
projectName: "solid-convex-fail",
frontend: ["solid"],
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "none",
api: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(
result,
"The following frontends are not compatible with '--backend convex': solid. Please choose a different frontend or backend.",
);
});
it("should work with React frontends + Convex", async () => {
const result = await runTRPCTest({
projectName: "react-convex",
frontend: ["tanstack-router"],
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "clerk",
api: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
});
describe("Frontend Compatibility with Auth", () => {
it("should fail incompatible frontends with Clerk + Convex", async () => {
const incompatibleFrontends = ["nuxt", "svelte", "solid"] as const;
for (const frontend of incompatibleFrontends) {
const result = await runTRPCTest({
projectName: `${frontend}-clerk-convex-fail`,
frontend: [frontend],
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "clerk",
api: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Clerk authentication is not compatible");
}
});
it("should work with compatible frontends + Clerk + Convex", async () => {
const compatibleFrontends = [
"tanstack-router",
"react-router",
"tanstack-start",
"next",
] as const;
for (const frontend of compatibleFrontends) {
const result = await runTRPCTest({
projectName: `${frontend}-clerk-convex`,
frontend: [frontend],
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "clerk",
api: "none",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
}
});
});
describe("Multiple Frontend Constraints", () => {
it("should fail with multiple web frontends", async () => {
const result = await runTRPCTest({
projectName: "multiple-web-fail",
frontend: ["tanstack-router", "react-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Cannot select multiple web frameworks");
});
it("should fail with multiple native frontends", async () => {
const result = await runTRPCTest({
projectName: "multiple-native-fail",
frontend: ["native-nativewind", "native-unistyles"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Cannot select multiple native frameworks");
});
it("should work with one web + one native frontend", async () => {
const result = await runTRPCTest({
projectName: "web-native-combo",
frontend: ["tanstack-router", "native-nativewind"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
});
describe("Frontend with None Option", () => {
it("should work with frontend none", async () => {
const result = await runTRPCTest({
projectName: "no-frontend",
frontend: ["none"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with none + other frontends", async () => {
const result = await runTRPCTest({
projectName: "none-with-other-fail",
frontend: ["none", "tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Cannot combine 'none' with other frontend options");
});
});
describe("Web Deploy Constraints", () => {
it("should work with web frontend + web deploy", async () => {
const result = await runTRPCTest({
projectName: "web-deploy",
frontend: ["tanstack-router"],
webDeploy: "wrangler",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should fail with web deploy but no web frontend", async () => {
const result = await runTRPCTest({
projectName: "web-deploy-no-frontend-fail",
frontend: ["native-nativewind"],
webDeploy: "wrangler",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
addons: ["none"],
examples: ["none"],
dbSetup: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "'--web-deploy' requires a web frontend");
});
});
});

View File

@@ -0,0 +1,83 @@
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { expectSuccess, runTRPCTest } from "./test-utils";
describe("CLI Test Suite", () => {
beforeAll(async () => {
// Ensure CLI is built before running tests
console.log("Setting up CLI tests...");
});
afterAll(async () => {
console.log("CLI tests completed.");
});
describe("Smoke Tests", () => {
it("should create a basic project successfully", async () => {
const result = await runTRPCTest({
projectName: "smoke-test-basic",
yes: true,
install: false,
});
expectSuccess(result);
});
it("should handle help command", async () => {
// This test would need to be implemented differently since it's not a project creation
// For now, we'll just test that the basic functionality works
expect(true).toBe(true);
});
it("should validate project name requirements", async () => {
const result = await runTRPCTest({
projectName: "valid-project-name",
yes: true,
install: false,
});
expectSuccess(result);
});
});
describe("Performance Tests", () => {
it("should complete project creation within reasonable time", async () => {
const startTime = Date.now();
const result = await runTRPCTest({
projectName: "performance-test",
yes: true,
install: false,
});
const endTime = Date.now();
const duration = endTime - startTime;
expectSuccess(result);
// Should complete within 30 seconds (without installation)
expect(duration).toBeLessThan(30000);
});
});
describe("Stability Tests", () => {
it("should handle multiple rapid project creations", async () => {
const promises = [];
for (let i = 0; i < 3; i++) {
promises.push(
runTRPCTest({
projectName: `stability-test-${i}`,
yes: true,
install: false,
}),
);
}
const results = await Promise.all(promises);
for (const result of results) {
expectSuccess(result);
}
});
});
});

View File

@@ -0,0 +1,541 @@
import { describe, it } from "vitest";
import type { Backend, Runtime } from "../src/types";
import {
expectError,
expectSuccess,
runTRPCTest,
type TestConfig,
} from "./test-utils";
describe("Integration Tests - Real World Scenarios", () => {
describe("Complete Stack Configurations", () => {
it("should create full-stack React app with tRPC", async () => {
const result = await runTRPCTest({
projectName: "fullstack-react-trpc",
backend: "hono",
runtime: "workers",
database: "postgres",
orm: "drizzle",
auth: "better-auth",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["biome", "turborepo"],
examples: ["todo", "ai"],
dbSetup: "none",
webDeploy: "wrangler",
serverDeploy: "wrangler",
install: false,
});
expectSuccess(result);
});
it("should create Nuxt app with oRPC", async () => {
const result = await runTRPCTest({
projectName: "nuxt-orpc-app",
backend: "hono",
runtime: "workers",
database: "sqlite",
orm: "drizzle",
auth: "better-auth",
api: "orpc",
frontend: ["nuxt"],
addons: ["biome", "husky"],
examples: ["ai"], // AI works with Nuxt
dbSetup: "none",
webDeploy: "alchemy",
serverDeploy: "alchemy",
install: false,
});
expectSuccess(result);
});
it("should create Svelte app with oRPC", async () => {
const result = await runTRPCTest({
projectName: "svelte-orpc-app",
backend: "hono",
runtime: "bun",
database: "mysql",
orm: "prisma",
auth: "better-auth",
api: "orpc",
frontend: ["svelte"],
addons: ["turborepo", "oxlint"],
examples: ["todo"], // Todo works with Svelte
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should create Convex app with Clerk auth", async () => {
const result = await runTRPCTest({
projectName: "convex-clerk-app",
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "clerk",
api: "none",
frontend: ["tanstack-router"],
addons: ["biome", "turborepo"],
examples: ["todo", "ai"],
dbSetup: "none",
webDeploy: "wrangler",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should create mobile app with React Native", async () => {
const result = await runTRPCTest({
projectName: "mobile-app",
backend: "hono",
runtime: "bun",
database: "postgres",
orm: "drizzle",
auth: "better-auth",
api: "trpc",
frontend: ["native-nativewind"],
addons: ["biome", "turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should create hybrid web + mobile app", async () => {
const result = await runTRPCTest({
projectName: "hybrid-web-mobile",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "better-auth",
api: "trpc",
frontend: ["tanstack-router", "native-unistyles"],
addons: ["biome", "turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should create Cloudflare Workers app", async () => {
const result = await runTRPCTest({
projectName: "cloudflare-workers-app",
backend: "hono",
runtime: "workers",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["biome"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "wrangler",
install: false,
});
expectSuccess(result);
});
it("should create MongoDB + Mongoose app", async () => {
const result = await runTRPCTest({
projectName: "mongodb-mongoose-app",
backend: "express",
runtime: "node",
database: "mongodb",
orm: "mongoose",
auth: "better-auth",
api: "trpc",
frontend: ["react-router"],
addons: ["husky", "turborepo"],
examples: ["todo"],
dbSetup: "none",
webDeploy: "alchemy",
serverDeploy: "alchemy",
install: false,
});
expectSuccess(result);
});
it("should create Next.js fullstack app", async () => {
const result = await runTRPCTest({
projectName: "nextjs-fullstack",
backend: "next",
runtime: "node",
database: "postgres",
orm: "prisma",
auth: "better-auth",
api: "trpc",
frontend: ["next"],
addons: ["biome", "turborepo", "pwa"],
examples: ["ai"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should create Solid.js app with oRPC", async () => {
const result = await runTRPCTest({
projectName: "solid-orpc-app",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "better-auth",
api: "orpc",
frontend: ["solid"],
addons: ["biome", "pwa"],
examples: ["todo"], // AI not compatible with Solid
dbSetup: "none",
webDeploy: "wrangler",
serverDeploy: "wrangler",
install: false,
});
expectSuccess(result);
});
});
describe("Frontend-only Configurations", () => {
it("should create frontend-only React app", async () => {
const result = await runTRPCTest({
projectName: "frontend-only-react",
backend: "none",
runtime: "none",
database: "none",
orm: "none",
auth: "none",
api: "none",
frontend: ["tanstack-router"],
addons: ["biome", "pwa"],
examples: ["none"],
dbSetup: "none",
webDeploy: "wrangler",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should create frontend-only Nuxt app", async () => {
const result = await runTRPCTest({
projectName: "frontend-only-nuxt",
backend: "none",
runtime: "none",
database: "none",
orm: "none",
auth: "none",
api: "none",
frontend: ["nuxt"],
addons: ["biome", "husky"],
examples: ["none"],
dbSetup: "none",
webDeploy: "alchemy",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
});
describe("Complex Error Scenarios", () => {
it("should fail with incompatible stack combination", async () => {
// MongoDB + Drizzle is not supported
const result = await runTRPCTest({
projectName: "incompatible-stack-fail",
backend: "hono",
runtime: "bun",
database: "mongodb",
orm: "drizzle", // Not compatible with MongoDB
auth: "better-auth",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Drizzle ORM does not support MongoDB");
});
it("should fail with workers + incompatible database", async () => {
const result = await runTRPCTest({
projectName: "workers-mongodb-fail",
backend: "hono",
runtime: "workers",
database: "mongodb", // Not compatible with Workers
orm: "mongoose",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "wrangler",
expectError: true,
});
expectError(
result,
"Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database",
);
});
it("should fail with tRPC + incompatible frontend", async () => {
const result = await runTRPCTest({
projectName: "trpc-nuxt-fail",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["nuxt"], // tRPC not compatible with Nuxt
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "tRPC API is not supported with 'nuxt' frontend");
});
it("should fail with Clerk + incompatible frontend", async () => {
const result = await runTRPCTest({
projectName: "clerk-svelte-fail",
backend: "convex",
runtime: "none",
database: "none",
orm: "none",
auth: "clerk",
api: "none",
frontend: ["svelte"], // Clerk + Convex not compatible with Svelte
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "Clerk authentication is not compatible");
});
it("should fail with addon incompatibility", async () => {
const result = await runTRPCTest({
projectName: "pwa-native-fail",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["native-nativewind"],
addons: ["pwa"], // PWA not compatible with native-only
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(result, "pwa addon requires one of these frontends");
});
it("should fail with example incompatibility", async () => {
const result = await runTRPCTest({
projectName: "ai-solid-fail",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "orpc",
frontend: ["solid"],
addons: ["none"],
examples: ["ai"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
expectError: true,
});
expectError(
result,
"The 'ai' example is not compatible with the Solid frontend",
);
});
it("should fail with deployment constraint violation", async () => {
const result = await runTRPCTest({
projectName: "web-deploy-no-frontend-fail",
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["native-nativewind"], // Only native, no web
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "wrangler", // Requires web frontend
serverDeploy: "none",
expectError: true,
});
expectError(result, "'--web-deploy' requires a web frontend");
});
});
describe("Edge Case Combinations", () => {
it("should handle maximum complexity configuration", async () => {
const result = await runTRPCTest({
projectName: "max-complexity",
backend: "hono",
runtime: "bun",
database: "postgres",
orm: "drizzle",
auth: "better-auth",
api: "trpc",
frontend: ["tanstack-router", "native-nativewind"],
addons: ["biome", "husky", "turborepo"],
examples: ["todo", "ai"],
dbSetup: "none",
webDeploy: "wrangler",
serverDeploy: "wrangler",
install: false,
});
expectSuccess(result);
});
it("should handle minimal configuration", async () => {
const result = await runTRPCTest({
projectName: "minimal-config",
backend: "none",
runtime: "none",
database: "none",
orm: "none",
auth: "none",
api: "none",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
});
it("should handle all package managers", async () => {
const packageManagers = ["npm", "pnpm", "bun"];
for (const packageManager of packageManagers) {
const result = await runTRPCTest({
projectName: `pkg-manager-${packageManager}`,
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
auth: "none",
api: "trpc",
frontend: ["tanstack-router"],
addons: ["none"],
examples: ["none"],
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
install: false,
});
expectSuccess(result);
}
});
it("should handle different runtime environments", async () => {
const runtimeConfigs = [
{ runtime: "bun", backend: "hono" },
{ runtime: "node", backend: "express" },
{ runtime: "workers", backend: "hono" },
{ runtime: "none", backend: "convex" },
];
for (const { runtime, backend } of runtimeConfigs) {
const config: TestConfig = {
projectName: `runtime-${runtime}-${backend}`,
runtime: runtime as Runtime,
backend: backend as Backend,
frontend: ["tanstack-router"],
install: false,
};
// Set appropriate defaults
if (backend === "convex") {
config.database = "none";
config.orm = "none";
config.auth = "clerk";
config.api = "none";
config.addons = ["none"];
config.examples = ["none"];
config.dbSetup = "none";
config.webDeploy = "none";
config.serverDeploy = "none";
} else {
config.database = "sqlite";
config.orm = "drizzle";
config.auth = "none";
config.api = "trpc";
config.addons = ["none"];
config.examples = ["none"];
config.dbSetup = "none";
config.webDeploy = "none";
config.serverDeploy = "none";
}
// Handle workers runtime requirements
if (runtime === "workers") {
config.serverDeploy = "wrangler";
}
const result = await runTRPCTest(config);
expectSuccess(result);
}
});
});
});

View File

@@ -1,561 +0,0 @@
import { join } from "node:path";
import { ensureDir, existsSync, readFile, remove } from "fs-extra";
import { parse as parseJsonc } from "jsonc-parser";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { init } from "../src/index";
import type { BetterTStackConfig } from "../src/types";
let testCounter = 0;
let tmpDir: string;
let originalCwd: string;
async function createTmpDir() {
testCounter++;
const dir = join(__dirname, "..", `.prog-test-${testCounter}`);
if (existsSync(dir)) {
await remove(dir);
}
await ensureDir(dir);
return dir;
}
function assertProjectExists(dir: string) {
expect(existsSync(dir)).toBe(true);
expect(existsSync(join(dir, "package.json"))).toBe(true);
expect(existsSync(join(dir, "bts.jsonc"))).toBe(true);
}
async function assertBtsConfig(
dir: string,
expectedConfig: Partial<{
frontend: string[];
backend: string;
database: string;
orm: string;
api: string;
runtime: string;
addons: string[];
}>,
) {
const configPath = join(dir, "bts.jsonc");
expect(existsSync(configPath)).toBe(true);
const configContent = await readFile(configPath, "utf-8");
const config: BetterTStackConfig = parseJsonc(configContent);
if (expectedConfig.frontend) {
expect(config.frontend).toEqual(expectedConfig.frontend);
}
if (expectedConfig.backend) {
expect(config.backend).toBe(expectedConfig.backend);
}
if (expectedConfig.database) {
expect(config.database).toBe(expectedConfig.database);
}
if (expectedConfig.orm) {
expect(config.orm).toBe(expectedConfig.orm);
}
if (expectedConfig.api) {
expect(config.api).toBe(expectedConfig.api);
}
if (expectedConfig.runtime) {
expect(config.runtime).toBe(expectedConfig.runtime);
}
if (expectedConfig.addons) {
expect(config.addons).toEqual(expectedConfig.addons);
}
}
describe("Programmatic API - Fast Tests", () => {
beforeEach(async () => {
originalCwd = process.cwd();
tmpDir = await createTmpDir();
process.chdir(tmpDir);
});
afterEach(async () => {
process.chdir(originalCwd);
if (existsSync(tmpDir)) {
await remove(tmpDir);
}
});
describe("Core functionality", () => {
test("creates minimal project successfully", async () => {
const result = await init("test-app", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
});
expect(result.success).toBe(true);
expect(result.projectConfig.projectName).toBe("test-app");
expect(result.projectDirectory).toContain("test-app");
expect(result.reproducibleCommand).toContain("test-app");
expect(typeof result.elapsedTimeMs).toBe("number");
expect(result.elapsedTimeMs).toBeGreaterThan(0);
assertProjectExists(result.projectDirectory);
}, 15000);
test("returns complete result structure", async () => {
const result = await init("result-test", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
});
expect(result).toHaveProperty("success");
expect(result).toHaveProperty("projectConfig");
expect(result).toHaveProperty("reproducibleCommand");
expect(result).toHaveProperty("timeScaffolded");
expect(result).toHaveProperty("elapsedTimeMs");
expect(result).toHaveProperty("projectDirectory");
expect(result).toHaveProperty("relativePath");
}, 15000);
test("handles project with custom name", async () => {
const result = await init("custom-name", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
});
expect(result.success).toBe(true);
expect(result.projectConfig.projectName).toBe("custom-name");
expect(result.projectDirectory).toContain("custom-name");
}, 15000);
});
describe("Configuration options", () => {
test("creates project with Next.js frontend", async () => {
const result = await init("next-app", {
frontend: ["next"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
});
expect(result.success).toBe(true);
await assertBtsConfig(result.projectDirectory, {
frontend: ["next"],
});
}, 15000);
test("creates project with Fastify backend", async () => {
const result = await init("fastify-app", {
frontend: ["tanstack-router"],
backend: "fastify",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
});
expect(result.success).toBe(true);
await assertBtsConfig(result.projectDirectory, {
backend: "fastify",
});
}, 15000);
test("creates project with PostgreSQL + Prisma", async () => {
const result = await init("pg-app", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "postgres",
orm: "prisma",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
});
expect(result.success).toBe(true);
await assertBtsConfig(result.projectDirectory, {
database: "postgres",
orm: "prisma",
});
}, 15000);
test("creates project with oRPC API", async () => {
const result = await init("orpc-app", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "orpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
});
expect(result.success).toBe(true);
await assertBtsConfig(result.projectDirectory, {
api: "orpc",
});
}, 15000);
test("creates project with Node runtime", async () => {
const result = await init("node-app", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "node",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
});
expect(result.success).toBe(true);
await assertBtsConfig(result.projectDirectory, {
runtime: "node",
});
}, 15000);
test("creates project with Biome addon", async () => {
const result = await init("biome-app", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["biome"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
});
expect(result.success).toBe(true);
await assertBtsConfig(result.projectDirectory, {
addons: ["biome"],
});
}, 15000);
test("creates project with analytics disabled", async () => {
const result = await init("no-analytics-app", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
disableAnalytics: true,
install: false,
git: false,
});
expect(result.success).toBe(true);
expect(result.projectConfig.projectName).toBe("no-analytics-app");
}, 15000);
});
describe("Error scenarios", () => {
test("handles invalid project name", async () => {
await expect(
init("", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
}),
).rejects.toThrow("Project name cannot be empty");
});
test("handles invalid characters in project name", async () => {
await expect(
init("invalid<name>", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
}),
).rejects.toThrow("invalid characters");
});
test("handles incompatible database + ORM combination", async () => {
await expect(
init("incompatible", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "mongodb",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
yolo: false,
}),
).rejects.toThrow(/Drizzle ORM does not support MongoDB/);
});
test("handles auth without database", async () => {
await expect(
init("auth-no-db", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "none",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
yolo: false,
}),
).rejects.toThrow(/ORM selection requires a database/);
});
test("handles directory conflict with error strategy", async () => {
const result1 = await init("conflict-test", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
});
expect(result1.success).toBe(true);
const result2 = await init("conflict-test", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
directoryConflict: "error",
});
expect(result2.success).toBe(false);
expect(result2.error).toMatch(/already exists/);
}, 20000);
});
describe("Advanced features", () => {
test("creates project with multiple addons", async () => {
const result = await init("multi-addon", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["biome", "turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
});
expect(result.success).toBe(true);
await assertBtsConfig(result.projectDirectory, {
addons: ["biome", "turborepo"],
});
}, 15000);
test("creates project with authentication enabled", async () => {
const result = await init("auth-app", {
frontend: ["tanstack-router"],
backend: "hono",
runtime: "bun",
database: "sqlite",
orm: "drizzle",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
});
expect(result.success).toBe(true);
await assertBtsConfig(result.projectDirectory, {
database: "sqlite",
orm: "drizzle",
});
expect(result.projectConfig.auth).toBe("better-auth");
}, 15000);
test("validates reproducible command format", async () => {
const result = await init("repro-test", {
frontend: ["next"],
backend: "fastify",
runtime: "bun",
database: "postgres",
orm: "prisma",
api: "trpc",
auth: "better-auth",
dbSetup: "none",
webDeploy: "none",
serverDeploy: "none",
addons: ["turborepo"],
examples: ["none"],
packageManager: "bun",
install: false,
git: false,
});
expect(result.success).toBe(true);
expect(result.reproducibleCommand).toContain("repro-test");
expect(result.reproducibleCommand).toContain("--frontend next");
expect(result.reproducibleCommand).toContain("--backend fastify");
expect(result.reproducibleCommand).toContain("--database postgres");
expect(result.reproducibleCommand).toContain("--orm prisma");
expect(result.reproducibleCommand).toContain("--no-install");
expect(result.reproducibleCommand).toContain("--no-git");
}, 15000);
});
});

245
apps/cli/test/test-utils.ts Normal file
View File

@@ -0,0 +1,245 @@
import { rm } from "node:fs/promises";
import { join } from "node:path";
import { ensureDir } from "fs-extra";
import { trpcServer } from "trpc-cli";
import { expect } from "vitest";
import { router } from "../src/index";
import type { CreateInput, InitResult } from "../src/types";
import {
AddonsSchema,
APISchema,
AuthSchema,
BackendSchema,
DatabaseSchema,
DatabaseSetupSchema,
ExamplesSchema,
FrontendSchema,
ORMSchema,
PackageManagerSchema,
RuntimeSchema,
ServerDeploySchema,
WebDeploySchema,
} from "../src/types";
// Create tRPC caller for direct function calls instead of subprocess
const t = trpcServer.initTRPC.create();
const defaultContext = {};
/**
* Clean up the entire .smoke directory
*/
export async function cleanupSmokeDirectory() {
const smokeDir = join(process.cwd(), ".smoke");
try {
await rm(smokeDir, { recursive: true, force: true });
} catch {
// Ignore cleanup errors
}
}
export interface TestResult {
success: boolean;
result?: InitResult;
error?: string;
projectDir?: string;
}
export interface TestConfig extends CreateInput {
projectName?: string;
expectError?: boolean;
expectedErrorMessage?: string;
}
/**
* Run tRPC test using direct function calls instead of subprocess
* This delegates all validation to the CLI's existing logic - much simpler!
*/
export async function runTRPCTest(config: TestConfig): Promise<TestResult> {
const smokeDir = join(process.cwd(), ".smoke");
await ensureDir(smokeDir);
// Store original environment
const originalProgrammatic = process.env.BTS_PROGRAMMATIC;
try {
// Set programmatic mode to ensure errors are thrown instead of process.exit
process.env.BTS_PROGRAMMATIC = "1";
const caller = t.createCallerFactory(router)(defaultContext);
const projectName = config.projectName || "default-app";
const projectPath = join(smokeDir, projectName);
// Determine if we should use --yes or not
// Only core stack flags conflict with --yes flag (from CLI error message)
const coreStackFlags: (keyof TestConfig)[] = [
"database",
"orm",
"backend",
"runtime",
"frontend",
"addons",
"examples",
"auth",
"dbSetup",
"api",
"webDeploy",
"serverDeploy",
];
const hasSpecificCoreConfig = coreStackFlags.some(
(flag) => config[flag] !== undefined,
);
// Only use --yes if no core stack flags are provided and not explicitly disabled
const willUseYesFlag =
config.yes !== undefined ? config.yes : !hasSpecificCoreConfig;
// Provide defaults for missing core stack options to avoid prompts
// But don't provide core stack defaults when yes: true is explicitly set
const coreStackDefaults = willUseYesFlag
? {}
: {
frontend: ["tanstack-router"] as Frontend[],
backend: "hono" as Backend,
runtime: "bun" as Runtime,
api: "trpc" as API,
database: "sqlite" as Database,
orm: "drizzle" as ORM,
auth: "none" as Auth,
addons: ["none"] as Addons[],
examples: ["none"] as Examples[],
dbSetup: "none" as DatabaseSetup,
webDeploy: "none" as WebDeploy,
serverDeploy: "none" as ServerDeploy,
};
// Build options object - let the CLI handle all validation
const options: CreateInput = {
renderTitle: false,
install: config.install ?? false,
git: config.git ?? true,
packageManager: config.packageManager ?? "bun",
directoryConflict: "overwrite",
verbose: true, // Need verbose to get the result
disableAnalytics: true,
yes: willUseYesFlag,
...coreStackDefaults,
...config,
};
// Remove our test-specific properties
const {
projectName: _,
expectError: __,
expectedErrorMessage: ___,
...cleanOptions
} = options as TestConfig;
const result = await caller.init([projectPath, cleanOptions]);
return {
success: result?.success ?? false,
result: result?.success ? result : undefined,
error: result?.success ? undefined : result?.error,
projectDir: result?.success ? result?.projectDirectory : undefined,
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error),
};
} finally {
// Always restore original environment
if (originalProgrammatic === undefined) {
delete process.env.BTS_PROGRAMMATIC;
} else {
process.env.BTS_PROGRAMMATIC = originalProgrammatic;
}
}
}
export function expectSuccess(result: TestResult) {
if (!result.success) {
console.error("Test failed:");
console.error("Error:", result.error);
if (result.result) {
console.error("Result:", result.result);
}
}
expect(result.success).toBe(true);
expect(result.result).toBeDefined();
}
export function expectError(result: TestResult, expectedMessage?: string) {
expect(result.success).toBe(false);
if (expectedMessage) {
expect(result.error).toContain(expectedMessage);
}
}
// Helper function to create properly typed test configs
export function createTestConfig(
config: Partial<TestConfig> & { projectName: string },
): TestConfig {
return config as TestConfig;
}
/**
* Extract enum values from a Zod enum schema
*/
function extractEnumValues<T extends string>(schema: {
options: readonly T[];
}): readonly T[] {
return schema.options;
}
// Inferred types and values from Zod schemas - no duplication with types.ts!
export type PackageManager = (typeof PackageManagerSchema)["options"][number];
export type Database = (typeof DatabaseSchema)["options"][number];
export type ORM = (typeof ORMSchema)["options"][number];
export type Backend = (typeof BackendSchema)["options"][number];
export type Runtime = (typeof RuntimeSchema)["options"][number];
export type Frontend = (typeof FrontendSchema)["options"][number];
export type Addons = (typeof AddonsSchema)["options"][number];
export type Examples = (typeof ExamplesSchema)["options"][number];
export type Auth = (typeof AuthSchema)["options"][number];
export type API = (typeof APISchema)["options"][number];
export type WebDeploy = (typeof WebDeploySchema)["options"][number];
export type ServerDeploy = (typeof ServerDeploySchema)["options"][number];
export type DatabaseSetup = (typeof DatabaseSetupSchema)["options"][number];
// Test data generators inferred from Zod schemas
export const PACKAGE_MANAGERS = extractEnumValues(PackageManagerSchema);
export const DATABASES = extractEnumValues(DatabaseSchema);
export const ORMS = extractEnumValues(ORMSchema);
export const BACKENDS = extractEnumValues(BackendSchema);
export const RUNTIMES = extractEnumValues(RuntimeSchema);
export const FRONTENDS = extractEnumValues(FrontendSchema);
export const ADDONS = extractEnumValues(AddonsSchema);
export const EXAMPLES = extractEnumValues(ExamplesSchema);
export const AUTH_PROVIDERS = extractEnumValues(AuthSchema);
export const API_TYPES = extractEnumValues(APISchema);
export const WEB_DEPLOYS = extractEnumValues(WebDeploySchema);
export const SERVER_DEPLOYS = extractEnumValues(ServerDeploySchema);
export const DB_SETUPS = extractEnumValues(DatabaseSetupSchema);
// Convenience functions for common test patterns
export function createBasicConfig(
overrides: Partial<TestConfig> = {},
): TestConfig {
return {
projectName: "test-app",
yes: true, // Use defaults
install: false,
git: true,
...overrides,
};
}
export function createCustomConfig(config: Partial<TestConfig>): TestConfig {
return {
projectName: "test-app",
install: false,
git: true,
...config,
};
}