mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
chore(cli): add tests (#576)
This commit is contained in:
@@ -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": {
|
||||
|
||||
@@ -174,7 +174,7 @@ export async function createProjectHandler(
|
||||
);
|
||||
}
|
||||
|
||||
await createProject(config);
|
||||
await createProject(config, { manualDb: input.manualDb });
|
||||
|
||||
const reproducibleCommand = generateReproducibleCommand(config);
|
||||
log.success(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
}),
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -147,6 +147,7 @@ export type CreateInput = {
|
||||
directoryConflict?: DirectoryConflict;
|
||||
renderTitle?: boolean;
|
||||
disableAnalytics?: boolean;
|
||||
manualDb?: boolean;
|
||||
};
|
||||
|
||||
export type AddInput = {
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
|
||||
|
||||
@@ -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.",
|
||||
|
||||
338
apps/cli/test/addons.test.ts
Normal file
338
apps/cli/test/addons.test.ts
Normal 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
639
apps/cli/test/api.test.ts
Normal 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
490
apps/cli/test/auth.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
393
apps/cli/test/backend-runtime.test.ts
Normal file
393
apps/cli/test/backend-runtime.test.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
176
apps/cli/test/basic-configurations.test.ts
Normal file
176
apps/cli/test/basic-configurations.test.ts
Normal 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",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
524
apps/cli/test/benchmark.test.ts
Normal file
524
apps/cli/test/benchmark.test.ts
Normal 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
237
apps/cli/test/database-orm.test.ts
Normal file
237
apps/cli/test/database-orm.test.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
506
apps/cli/test/database-setup.test.ts
Normal file
506
apps/cli/test/database-setup.test.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
560
apps/cli/test/deployment.test.ts
Normal file
560
apps/cli/test/deployment.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
});
|
||||
444
apps/cli/test/examples.test.ts
Normal file
444
apps/cli/test/examples.test.ts
Normal 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",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
454
apps/cli/test/frontend.test.ts
Normal file
454
apps/cli/test/frontend.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
});
|
||||
83
apps/cli/test/index.test.ts
Normal file
83
apps/cli/test/index.test.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
541
apps/cli/test/integration.test.ts
Normal file
541
apps/cli/test/integration.test.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
245
apps/cli/test/test-utils.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user