diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..e13e7cf0 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,46 @@ +name: Test + +on: + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup Git user + shell: bash + run: | + git config --global user.name github-actions[bot] + git config --global user.email 41898282+github-actions[bot]@users.noreply.github.com + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Install Dependencies + run: bun install --frozen-lockfile + env: + BTS_TELEMETRY: 1 + POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} + POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} + + - name: Run Tests + run: bun test --timeout 15000 + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: ./coverage/lcov.info + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 970c1537..1cee2908 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,6 @@ yarn-error.log* .vscode .env*.local -.smoke \ No newline at end of file +.smoke + +.idea \ No newline at end of file diff --git a/apps/cli/test/addons.test.ts b/apps/cli/test/addons.test.ts index b9e08751..c9947beb 100644 --- a/apps/cli/test/addons.test.ts +++ b/apps/cli/test/addons.test.ts @@ -1,6 +1,7 @@ -import { describe, it } from "vitest"; +import { afterAll, beforeAll, describe, it } from "vitest"; import type { Addons, Frontend } from "../src"; import { + cleanupSmokeDirectory, expectError, expectSuccess, runTRPCTest, @@ -8,6 +9,14 @@ import { } from "./test-utils"; describe("Addon Configurations", () => { + beforeAll(async () => { + await cleanupSmokeDirectory(); + }); + + afterAll(async () => { + await cleanupSmokeDirectory(); + }); + describe("Universal Addons (no frontend restrictions)", () => { const universalAddons = ["biome", "husky", "turborepo", "oxlint"]; diff --git a/apps/cli/test/api.test.ts b/apps/cli/test/api.test.ts index 385098f4..ff07bd62 100644 --- a/apps/cli/test/api.test.ts +++ b/apps/cli/test/api.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "vitest"; +import { afterAll, beforeAll, describe, it } from "vitest"; import type { API, Backend, @@ -10,6 +10,7 @@ import type { } from "../src/types"; import { API_TYPES, + cleanupSmokeDirectory, expectError, expectSuccess, runTRPCTest, @@ -17,6 +18,14 @@ import { } from "./test-utils"; describe("API Configurations", () => { + beforeAll(async () => { + await cleanupSmokeDirectory(); + }); + + afterAll(async () => { + await cleanupSmokeDirectory(); + }); + describe("tRPC API", () => { it("should work with tRPC + React frontends", async () => { const reactFrontends = [ diff --git a/apps/cli/test/auth.test.ts b/apps/cli/test/auth.test.ts index 656966ea..e6b79171 100644 --- a/apps/cli/test/auth.test.ts +++ b/apps/cli/test/auth.test.ts @@ -1,7 +1,8 @@ -import { describe, it } from "vitest"; +import { afterAll, beforeAll, describe, it } from "vitest"; import type { Backend, Database, Frontend, ORM } from "../src/types"; import { AUTH_PROVIDERS, + cleanupSmokeDirectory, expectError, expectSuccess, runTRPCTest, @@ -9,6 +10,14 @@ import { } from "./test-utils"; describe("Authentication Configurations", () => { + beforeAll(async () => { + await cleanupSmokeDirectory(); + }); + + afterAll(async () => { + await cleanupSmokeDirectory(); + }); + describe("Better-Auth Provider", () => { it("should work with better-auth + database", async () => { const result = await runTRPCTest({ @@ -95,7 +104,10 @@ describe("Authentication Configurations", () => { expectError: true, }); - expectError(result, "Authentication requires a database"); + expectError( + result, + "The 'todo' example requires a database if a backend (other than Convex) is present. Cannot use --examples todo when database is 'none' and a backend is selected.", + ); }); it("should work with better-auth + convex backend (tanstack-router)", async () => { diff --git a/apps/cli/test/backend-runtime.test.ts b/apps/cli/test/backend-runtime.test.ts index 548ff6d8..251801b7 100644 --- a/apps/cli/test/backend-runtime.test.ts +++ b/apps/cli/test/backend-runtime.test.ts @@ -1,6 +1,7 @@ -import { describe, it } from "vitest"; +import { afterAll, beforeAll, describe, it } from "vitest"; import type { Backend, Frontend, Runtime } from "../src/types"; import { + cleanupSmokeDirectory, expectError, expectSuccess, runTRPCTest, @@ -8,6 +9,14 @@ import { } from "./test-utils"; describe("Backend and Runtime Combinations", () => { + beforeAll(async () => { + await cleanupSmokeDirectory(); + }); + + afterAll(async () => { + await cleanupSmokeDirectory(); + }); + describe("Valid Backend-Runtime Combinations", () => { const validCombinations = [ // Standard backend-runtime combinations @@ -464,7 +473,7 @@ describe("Backend and Runtime Combinations", () => { expectError( result, - "Backend 'self' (fullstack) currently only supports Next.js frontend. Please use --frontend next. Support for Nuxt, SvelteKit, and TanStack Start will be added in a future update.", + "Backend 'self' (fullstack) currently only supports Next.js and TanStack Start frontends. Please use --frontend next or --frontend tanstack-start. Support for Nuxt and SvelteKit will be added in a future update.", ); }); diff --git a/apps/cli/test/basic-configurations.test.ts b/apps/cli/test/basic-configurations.test.ts index a363d490..c6389bfb 100644 --- a/apps/cli/test/basic-configurations.test.ts +++ b/apps/cli/test/basic-configurations.test.ts @@ -1,5 +1,6 @@ -import { describe, expect, it } from "vitest"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { + cleanupSmokeDirectory, expectError, expectSuccess, PACKAGE_MANAGERS, @@ -7,6 +8,14 @@ import { } from "./test-utils"; describe("Basic Configurations", () => { + beforeAll(async () => { + await cleanupSmokeDirectory(); + }); + + afterAll(async () => { + await cleanupSmokeDirectory(); + }); + describe("Default Configuration", () => { it("should create project with --yes flag (default config)", async () => { const result = await runTRPCTest({ diff --git a/apps/cli/test/config-package.test.ts b/apps/cli/test/config-package.test.ts deleted file mode 100644 index 0d6e8249..00000000 --- a/apps/cli/test/config-package.test.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { afterAll, beforeAll, describe, it } from "vitest"; -import { - cleanupSmokeDirectory, - runTRPCTest, - validateConfigPackageSetup, - validateTurboPrune, -} from "./test-utils"; - -describe("Config Package Feature", () => { - beforeAll(async () => { - await cleanupSmokeDirectory(); - }); - afterAll(async () => { - await cleanupSmokeDirectory(); - }); - - describe("Basic Stack Configurations", () => { - it("should validate hono + pnpm + turbo stack", async () => { - const result = await runTRPCTest({ - projectName: "hono-pnpm", - backend: "hono", - runtime: "node", - packageManager: "pnpm", - database: "sqlite", - orm: "drizzle", - api: "trpc", - frontend: ["tanstack-router"], - addons: ["turborepo"], - install: false, - }); - await validateConfigPackageSetup(result); - await validateTurboPrune(result); - }); - - it("should validate hono + pnpm + turbo + wrangler stack", async () => { - const result = await runTRPCTest({ - projectName: "hono-pnpm-turbo-wrangler", - backend: "hono", - runtime: "workers", - packageManager: "pnpm", - database: "sqlite", - orm: "drizzle", - api: "trpc", - frontend: ["tanstack-router"], - addons: ["turborepo"], - serverDeploy: "wrangler", - webDeploy: "wrangler", - install: false, - }); - await validateConfigPackageSetup(result); - await validateTurboPrune(result); - }); - - it("should validate hono + node stack", async () => { - const result = await runTRPCTest({ - projectName: "hono-node", - backend: "hono", - runtime: "node", - database: "sqlite", - orm: "drizzle", - api: "trpc", - frontend: ["tanstack-router"], - install: false, - }); - await validateConfigPackageSetup(result); - }); - - it("should validate hono + bun stack", async () => { - const result = await runTRPCTest({ - projectName: "hono-bun", - backend: "hono", - runtime: "bun", - database: "sqlite", - orm: "drizzle", - api: "trpc", - frontend: ["tanstack-router"], - install: false, - }); - await validateConfigPackageSetup(result); - }); - - it("should validate express + node stack", async () => { - const result = await runTRPCTest({ - projectName: "express-node", - backend: "express", - runtime: "node", - database: "sqlite", - orm: "drizzle", - frontend: ["tanstack-router"], - install: false, - }); - await validateConfigPackageSetup(result); - }); - - it("should validate fastify + node stack", async () => { - const result = await runTRPCTest({ - projectName: "fastify-node", - backend: "fastify", - runtime: "node", - frontend: ["tanstack-router"], - install: false, - }); - await validateConfigPackageSetup(result); - }); - }); - - describe("Full Stack with Authentication", () => { - it("should validate full stack with better-auth", async () => { - const result = await runTRPCTest({ - projectName: "full-stack-auth", - backend: "hono", - runtime: "node", - database: "postgres", - orm: "drizzle", - api: "trpc", - auth: "better-auth", - frontend: ["tanstack-router"], - addons: ["turborepo"], - install: false, - }); - await validateConfigPackageSetup(result); - await validateTurboPrune(result); - }); - }); - - describe("API Variants", () => { - it("should validate stack with tRPC", async () => { - const result = await runTRPCTest({ - projectName: "trpc-api", - backend: "hono", - runtime: "node", - api: "trpc", - database: "sqlite", - orm: "drizzle", - frontend: ["tanstack-router"], - install: false, - }); - await validateConfigPackageSetup(result); - }); - - it("should validate stack with oRPC", async () => { - const result = await runTRPCTest({ - projectName: "orpc-api", - backend: "hono", - runtime: "node", - api: "orpc", - database: "sqlite", - orm: "drizzle", - frontend: ["tanstack-router"], - install: false, - }); - await validateConfigPackageSetup(result); - }); - }); - - describe("Edge Cases", () => { - it("should validate frontend-only stack (no backend)", async () => { - const result = await runTRPCTest({ - projectName: "frontend-only", - backend: "none", - runtime: "none", - database: "none", - orm: "none", - api: "none", - auth: "none", - frontend: ["tanstack-router"], - install: false, - }); - await validateConfigPackageSetup(result); - }); - - it("should validate convex backend", async () => { - const result = await runTRPCTest({ - projectName: "convex-backend", - backend: "convex", - runtime: "none", - database: "none", - orm: "none", - api: "none", - frontend: ["next"], - install: false, - }); - await validateConfigPackageSetup(result); - }); - - it("should validate self-hosted backend", async () => { - const result = await runTRPCTest({ - projectName: "self-backend", - backend: "self", - runtime: "none", - api: "trpc", - database: "sqlite", - orm: "drizzle", - frontend: ["next"], - install: false, - }); - await validateConfigPackageSetup(result); - }); - - it("should validate stack without database", async () => { - const result = await runTRPCTest({ - projectName: "no-database", - backend: "hono", - runtime: "node", - database: "none", - orm: "none", - api: "trpc", - frontend: ["tanstack-router"], - install: false, - }); - await validateConfigPackageSetup(result); - }); - - it("should validate stack with turborepo addon", async () => { - const result = await runTRPCTest({ - projectName: "with-turborepo", - backend: "hono", - runtime: "node", - database: "sqlite", - orm: "drizzle", - frontend: ["tanstack-router"], - addons: ["turborepo"], - install: false, - }); - await validateConfigPackageSetup(result); - await validateTurboPrune(result); - }); - }); -}); diff --git a/apps/cli/test/database-orm.test.ts b/apps/cli/test/database-orm.test.ts index 23a2d761..ad13a5e1 100644 --- a/apps/cli/test/database-orm.test.ts +++ b/apps/cli/test/database-orm.test.ts @@ -1,6 +1,7 @@ -import { describe, it } from "vitest"; +import { afterAll, beforeAll, describe, it } from "vitest"; import type { Database, ORM } from "../src/types"; import { + cleanupSmokeDirectory, DATABASES, expectError, expectSuccess, @@ -8,6 +9,14 @@ import { } from "./test-utils"; describe("Database and ORM Combinations", () => { + beforeAll(async () => { + await cleanupSmokeDirectory(); + }); + + afterAll(async () => { + await cleanupSmokeDirectory(); + }); + describe("Valid Database-ORM Combinations", () => { const validCombinations: Array<{ database: Database; orm: ORM }> = [ // SQLite combinations @@ -155,7 +164,7 @@ describe("Database and ORM Combinations", () => { expectSuccess(result); }); - it("should fail with auth but no database (non-convex backend)", async () => { + it("should work with auth but no database (non-convex backend)", async () => { const result = await runTRPCTest({ projectName: "auth-no-db", database: "none", @@ -173,7 +182,7 @@ describe("Database and ORM Combinations", () => { expectError: true, }); - expectError(result, "Authentication requires a database"); + expectSuccess(result); }); it("should work with auth but no database (convex backend)", async () => { diff --git a/apps/cli/test/database-setup.test.ts b/apps/cli/test/database-setup.test.ts index 79c5d171..7cc885c7 100644 --- a/apps/cli/test/database-setup.test.ts +++ b/apps/cli/test/database-setup.test.ts @@ -1,5 +1,6 @@ -import { describe, it } from "vitest"; +import { afterAll, beforeAll, describe, it } from "vitest"; import { + cleanupSmokeDirectory, DB_SETUPS, expectError, expectSuccess, @@ -8,6 +9,14 @@ import { } from "./test-utils"; describe("Database Setup Configurations", () => { + beforeAll(async () => { + await cleanupSmokeDirectory(); + }); + + afterAll(async () => { + await cleanupSmokeDirectory(); + }); + describe("SQLite Database Setups", () => { it("should work with Turso + SQLite", async () => { const result = await runTRPCTest({ diff --git a/apps/cli/test/deployment.test.ts b/apps/cli/test/deployment.test.ts index b1489ef5..a2fa963d 100644 --- a/apps/cli/test/deployment.test.ts +++ b/apps/cli/test/deployment.test.ts @@ -1,5 +1,6 @@ -import { describe, it } from "vitest"; +import { afterAll, beforeAll, describe, it } from "vitest"; import { + cleanupSmokeDirectory, expectError, expectSuccess, runTRPCTest, @@ -9,6 +10,14 @@ import { } from "./test-utils"; describe("Deployment Configurations", () => { + beforeAll(async () => { + await cleanupSmokeDirectory(); + }); + + afterAll(async () => { + await cleanupSmokeDirectory(); + }); + describe("Web Deployment", () => { describe("Valid Web Deploy Configurations", () => { for (const webDeploy of WEB_DEPLOYS) { @@ -152,7 +161,10 @@ describe("Deployment Configurations", () => { webDeploy: "none", serverDeploy: serverDeploy, backend: "hono", - runtime: "bun", + runtime: + serverDeploy === "wrangler" || serverDeploy === "alchemy" + ? "workers" + : "bun", database: "sqlite", orm: "drizzle", auth: "none", @@ -221,7 +233,6 @@ describe("Deployment Configurations", () => { const config: TestConfig = { projectName: `server-deploy-${backend}`, webDeploy: "none", - serverDeploy: "wrangler", backend, database: "sqlite", orm: "drizzle", @@ -237,6 +248,9 @@ describe("Deployment Configurations", () => { // Set appropriate runtime if (backend === "elysia") { config.runtime = "bun"; + } else if (backend === "hono") { + config.runtime = "workers"; + config.serverDeploy = "wrangler"; } else { config.runtime = "bun"; } @@ -322,7 +336,7 @@ describe("Deployment Configurations", () => { webDeploy: "wrangler", serverDeploy: "wrangler", backend: "hono", - runtime: "bun", + runtime: "workers", database: "sqlite", orm: "drizzle", auth: "none", @@ -385,7 +399,7 @@ describe("Deployment Configurations", () => { webDeploy: "none", serverDeploy: "wrangler", backend: "hono", - runtime: "bun", + runtime: "workers", database: "sqlite", orm: "drizzle", auth: "none", @@ -502,6 +516,10 @@ describe("Deployment Configurations", () => { config.backend = "hono"; // Ensure backend for server deploy } + if (serverDeploy !== "none" || config.backend === "none") { + config.runtime = "workers"; // Ensure runtime + } + const result = await runTRPCTest(config); expectSuccess(result); }); diff --git a/apps/cli/test/examples.test.ts b/apps/cli/test/examples.test.ts index f3f966e2..a1c0ecb6 100644 --- a/apps/cli/test/examples.test.ts +++ b/apps/cli/test/examples.test.ts @@ -1,5 +1,6 @@ -import { describe, it } from "vitest"; +import { afterAll, beforeAll, describe, it } from "vitest"; import { + cleanupSmokeDirectory, EXAMPLES, expectError, expectSuccess, @@ -8,6 +9,14 @@ import { } from "./test-utils"; describe("Example Configurations", () => { + beforeAll(async () => { + await cleanupSmokeDirectory(); + }); + + afterAll(async () => { + await cleanupSmokeDirectory(); + }); + describe("Todo Example", () => { it("should work with todo example + database + backend", async () => { const result = await runTRPCTest({ diff --git a/apps/cli/test/frontend.test.ts b/apps/cli/test/frontend.test.ts index 671b1398..c8f629bf 100644 --- a/apps/cli/test/frontend.test.ts +++ b/apps/cli/test/frontend.test.ts @@ -1,5 +1,6 @@ -import { describe, it } from "vitest"; +import { afterAll, beforeAll, describe, it } from "vitest"; import { + cleanupSmokeDirectory, expectError, expectSuccess, runTRPCTest, @@ -7,6 +8,14 @@ import { } from "./test-utils"; describe("Frontend Configurations", () => { + beforeAll(async () => { + await cleanupSmokeDirectory(); + }); + + afterAll(async () => { + await cleanupSmokeDirectory(); + }); + describe("Single Frontend Options", () => { const singleFrontends = [ "tanstack-router", diff --git a/apps/cli/test/index.test.ts b/apps/cli/test/index.test.ts index a9ae2f47..f9ac7d2c 100644 --- a/apps/cli/test/index.test.ts +++ b/apps/cli/test/index.test.ts @@ -1,14 +1,20 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import { expectSuccess, runTRPCTest } from "./test-utils"; +import { + cleanupSmokeDirectory, + expectSuccess, + runTRPCTest, +} from "./test-utils"; describe("CLI Test Suite", () => { beforeAll(async () => { // Ensure CLI is built before running tests console.log("Setting up CLI tests..."); + await cleanupSmokeDirectory(); }); afterAll(async () => { console.log("CLI tests completed."); + await cleanupSmokeDirectory(); }); describe("Smoke Tests", () => { diff --git a/apps/cli/test/integration.test.ts b/apps/cli/test/integration.test.ts index 02865512..733d2197 100644 --- a/apps/cli/test/integration.test.ts +++ b/apps/cli/test/integration.test.ts @@ -1,6 +1,7 @@ -import { describe, it } from "vitest"; +import { afterAll, beforeAll, describe, it } from "vitest"; import type { Backend, Runtime } from "../src/types"; import { + cleanupSmokeDirectory, expectError, expectSuccess, runTRPCTest, @@ -8,6 +9,14 @@ import { } from "./test-utils"; describe("Integration Tests - Real World Scenarios", () => { + beforeAll(async () => { + await cleanupSmokeDirectory(); + }); + + afterAll(async () => { + await cleanupSmokeDirectory(); + }); + describe("Complete Stack Configurations", () => { it("should create full-stack React app with tRPC", async () => { const result = await runTRPCTest({ @@ -190,8 +199,8 @@ describe("Integration Tests - Real World Scenarios", () => { addons: ["husky", "turborepo"], examples: ["todo"], dbSetup: "none", - webDeploy: "alchemy", - serverDeploy: "alchemy", + webDeploy: "none", + serverDeploy: "none", install: false, }); @@ -223,7 +232,7 @@ describe("Integration Tests - Real World Scenarios", () => { const result = await runTRPCTest({ projectName: "solid-orpc-app", backend: "hono", - runtime: "bun", + runtime: "workers", database: "sqlite", orm: "drizzle", auth: "better-auth", @@ -468,7 +477,7 @@ describe("Integration Tests - Real World Scenarios", () => { const result = await runTRPCTest({ projectName: "max-complexity", backend: "hono", - runtime: "bun", + runtime: "workers", database: "postgres", orm: "drizzle", auth: "better-auth", diff --git a/apps/cli/test/test-utils.ts b/apps/cli/test/test-utils.ts index 162f483d..8f62b1c5 100644 --- a/apps/cli/test/test-utils.ts +++ b/apps/cli/test/test-utils.ts @@ -174,237 +174,6 @@ export function expectSuccess(result: TestResult) { expect(result.result).toBeDefined(); } -export function expectSuccessWithProjectDir(result: TestResult): string { - expectSuccess(result); - expect(result.projectDir).toBeDefined(); - return result.projectDir as string; -} - -/** - * Helper functions for generating expected config package references - */ -export function configPackageName(projectName: string): string { - return `@${projectName}/config`; -} - -export function configTsConfigReference(projectName: string): string { - return `@${projectName}/config/tsconfig.base.json`; -} - -/** - * Comprehensive validation helper for config package setup - * Validates all expected files and references based on the CreateInput configuration - */ -export async function validateConfigPackageSetup( - result: TestResult, -): Promise { - const { pathExists, readFile, readJSON } = await import("fs-extra"); - const { join } = await import("node:path"); - const { expect } = await import("vitest"); - - // Extract data from result - const projectDir = expectSuccessWithProjectDir(result); - const config = result.config; - const projectName = config.projectName as string; - - // 1. Config package structure - expect(await pathExists(join(projectDir, "packages/config"))).toBe(true); - - // 2. Config package.json - const configPkgJson = await readJSON( - join(projectDir, "packages/config/package.json"), - ); - expect(configPkgJson.name).toBe(configPackageName(projectName)); - expect(configPkgJson.private).toBe(true); - - // 3. Config tsconfig.base.json - const configTsConfigBase = await readJSON( - join(projectDir, "packages/config/tsconfig.base.json"), - ); - expect(configTsConfigBase.compilerOptions).toBeDefined(); - expect(configTsConfigBase.compilerOptions.strict).toBe(true); - - // Check runtime-specific types - if ( - config.runtime === "node" || - config.runtime === "workers" || - config.runtime === "none" - ) { - expect(configTsConfigBase.compilerOptions.types).toContain("node"); - } else if (config.runtime === "bun") { - expect(configTsConfigBase.compilerOptions.types).toContain("bun"); - } - - // 4. Config tsconfig.json - expect( - await pathExists(join(projectDir, "packages/config/tsconfig.json")), - ).toBe(true); - - // 5. Root configuration - expect(await pathExists(join(projectDir, "tsconfig.base.json"))).toBe(false); - - const rootTsConfig = await readFile( - join(projectDir, "tsconfig.json"), - "utf-8", - ); - expect(rootTsConfig).toContain(configTsConfigReference(projectName)); - - const rootPkgJson = await readJSON(join(projectDir, "package.json")); - expect(rootPkgJson.devDependencies[configPackageName(projectName)]).toBe( - "workspace:*", - ); - - // 6. Workspace packages based on config - const shouldHaveDb = - config.database && config.database !== "none" && config.orm !== "none"; - const shouldHaveApi = config.api && config.api !== "none"; - const shouldHaveAuth = config.auth && config.auth !== "none"; - const shouldHaveServer = - config.backend && !["none", "convex", "self"].includes(config.backend); - - if (shouldHaveDb) { - const dbTsConfig = await readFile( - join(projectDir, "packages/db/tsconfig.json"), - "utf-8", - ); - expect(dbTsConfig).toContain(configTsConfigReference(projectName)); - - const dbPkgJson = await readJSON( - join(projectDir, "packages/db/package.json"), - ); - expect(dbPkgJson.devDependencies[configPackageName(projectName)]).toBe( - "workspace:*", - ); - } - - if (shouldHaveApi) { - const apiTsConfig = await readFile( - join(projectDir, "packages/api/tsconfig.json"), - "utf-8", - ); - expect(apiTsConfig).toContain(configTsConfigReference(projectName)); - - const apiPkgJson = await readJSON( - join(projectDir, "packages/api/package.json"), - ); - expect(apiPkgJson.devDependencies[configPackageName(projectName)]).toBe( - "workspace:*", - ); - } - - if (shouldHaveAuth) { - const authTsConfig = await readFile( - join(projectDir, "packages/auth/tsconfig.json"), - "utf-8", - ); - expect(authTsConfig).toContain(configTsConfigReference(projectName)); - - const authPkgJson = await readJSON( - join(projectDir, "packages/auth/package.json"), - ); - expect(authPkgJson.devDependencies[configPackageName(projectName)]).toBe( - "workspace:*", - ); - } - - if (shouldHaveServer) { - const serverTsConfig = await readFile( - join(projectDir, "apps/server/tsconfig.json"), - "utf-8", - ); - expect(serverTsConfig).toContain(configTsConfigReference(projectName)); - - const serverPkgJson = await readJSON( - join(projectDir, "apps/server/package.json"), - ); - expect(serverPkgJson.devDependencies[configPackageName(projectName)]).toBe( - "workspace:*", - ); - } -} - -/** - * Validate that turbo prune works correctly for turborepo projects - * Only runs if the project has turborepo addon enabled - * Generates lockfile without installing dependencies, then runs turbo prune - */ -export async function validateTurboPrune(result: TestResult): Promise { - const { expect } = await import("vitest"); - const { execa } = await import("execa"); - - // Extract data from result - const projectDir = expectSuccessWithProjectDir(result); - const config = result.config; - - // Only run this validation if turborepo addon is enabled - const hasTurborepo = - config.addons && - Array.isArray(config.addons) && - config.addons.includes("turborepo"); - - if (!hasTurborepo) { - return; - } - - const packageManager = config.packageManager || "pnpm"; - - // Generate lockfile without installing dependencies - try { - if (packageManager === "pnpm") { - await execa("pnpm", ["install", "--lockfile-only"], { - cwd: projectDir, - }); - } else if (packageManager === "npm") { - await execa("npm", ["install", "--package-lock-only"], { - cwd: projectDir, - }); - } else if (packageManager === "bun") { - // Bun doesn't have --lockfile-only, so we skip for bun - // or use bun install which is fast anyway - await execa("bun", ["install", "--frozen-lockfile"], { - cwd: projectDir, - reject: false, // Don't fail if frozen-lockfile doesn't exist - }); - } - } catch (error) { - console.error("Failed to generate lockfile:"); - console.error(error); - expect.fail( - `Failed to generate lockfile: ${error instanceof Error ? error.message : String(error)}`, - ); - } - - // Determine package manager command for running turbo - const command = - packageManager === "npm" - ? "npx" - : packageManager === "bun" - ? "bunx" - : "pnpm"; - - // Test turbo prune for both server and web targets - const targets = ["server", "web"]; - - for (const target of targets) { - const args = - packageManager === "pnpm" - ? ["dlx", "turbo", "prune", target, "--docker"] - : ["turbo", "prune", target, "--docker"]; - - try { - await execa(command, args, { - cwd: projectDir, - }); - } catch (error) { - console.error(`turbo prune ${target} failed:`); - console.error(error); - expect.fail( - `turbo prune ${target} --docker failed: ${error instanceof Error ? error.message : String(error)}`, - ); - } - } -} - export function expectError(result: TestResult, expectedMessage?: string) { expect(result.success).toBe(false); if (expectedMessage) { diff --git a/bunfig.toml b/bunfig.toml index 641e878f..b4ba9362 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,2 +1,7 @@ [install] linker = "isolated" + +[test] +coverage = true +coverageReporter = ["text", "lcov"] +coverageSkipTestFiles = true # default false \ No newline at end of file