Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/cli/src/helpers/core/template-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,16 @@ async function setupApiPackage(projectDir: string, context: ProjectConfig) {
}
}

async function setupConfigPackage(projectDir: string, context: ProjectConfig) {
const configPackageDir = path.join(projectDir, "packages/config");
await fs.ensureDir(configPackageDir);

const configBaseDir = path.join(PKG_ROOT, "templates/packages/config");
if (await fs.pathExists(configBaseDir)) {
await processAndCopyFiles("**/*", configBaseDir, configPackageDir, context);
}
}

async function setupDbPackage(projectDir: string, context: ProjectConfig) {
if (context.database === "none" || context.orm === "none") return;

Expand Down Expand Up @@ -345,6 +355,8 @@ export async function setupBackendFramework(
projectDir: string,
context: ProjectConfig,
) {
await setupConfigPackage(projectDir, context);

if (context.backend === "none") {
return;
}
Expand Down
12 changes: 12 additions & 0 deletions apps/cli/src/helpers/core/workspace-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@ export async function setupWorkspaceDependencies(
const commonDeps: AvailableDependencies[] = ["dotenv", "zod"];
const commonDevDeps: AvailableDependencies[] = ["tsdown"];

const configPackageDir = path.join(projectDir, "packages/config");
const configDep: Record<string, string> = {};
if (await fs.pathExists(configPackageDir)) {
configDep[`@${projectName}/config`] = workspaceVersion;
}

const dbPackageDir = path.join(projectDir, "packages/db");
if (await fs.pathExists(dbPackageDir)) {
await addPackageDependency({
dependencies: commonDeps,
devDependencies: commonDevDeps,
customDevDependencies: configDep,
projectDir: dbPackageDir,
});
}
Expand All @@ -35,6 +42,7 @@ export async function setupWorkspaceDependencies(
dependencies: commonDeps,
devDependencies: commonDevDeps,
customDependencies: authDeps,
customDevDependencies: configDep,
projectDir: authPackageDir,
});
}
Expand All @@ -53,6 +61,7 @@ export async function setupWorkspaceDependencies(
dependencies: commonDeps,
devDependencies: commonDevDeps,
customDependencies: apiDeps,
customDevDependencies: configDep,
projectDir: apiPackageDir,
});
}
Expand All @@ -74,6 +83,7 @@ export async function setupWorkspaceDependencies(
dependencies: commonDeps,
devDependencies: commonDevDeps,
customDependencies: serverDeps,
customDevDependencies: configDep,
projectDir: serverPackageDir,
});
}
Expand All @@ -92,6 +102,7 @@ export async function setupWorkspaceDependencies(
if (Object.keys(webDeps).length > 0) {
await addPackageDependency({
customDependencies: webDeps,
customDevDependencies: configDep,
projectDir: webPackageDir,
});
}
Expand All @@ -108,6 +119,7 @@ export async function setupWorkspaceDependencies(
if (Object.keys(nativeDeps).length > 0) {
await addPackageDependency({
customDependencies: nativeDeps,
customDevDependencies: configDep,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Root Package Missing Dev Dependency

The root package.json doesn't get the config package added as a dev dependency. The addPackageDependency call for projectDir is missing customDevDependencies: configDep, but the test validation at line 253-255 expects @{projectName}/config to be in the root's devDependencies. All other packages correctly receive this dependency.

Fix in Cursor Fix in Web

projectDir: nativePackageDir,
});
}
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/templates/api/orpc/server/tsconfig.json.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"extends": "@{{projectName}}/config/tsconfig.base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/templates/api/trpc/server/tsconfig.json.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"extends": "@{{projectName}}/config/tsconfig.base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"extends": "@{{projectName}}/config/tsconfig.base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/templates/backend/server/base/tsconfig.json.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"extends": "@{{projectName}}/config/tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "dist",
Expand Down
3 changes: 0 additions & 3 deletions apps/cli/templates/base/tsconfig.json.hbs

This file was deleted.

2 changes: 1 addition & 1 deletion apps/cli/templates/db/base/tsconfig.json.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"extends": "@{{projectName}}/config/tsconfig.base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
Expand Down
5 changes: 5 additions & 0 deletions apps/cli/templates/packages/config/package.json.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "@{{projectName}}/config",
"version": "0.0.0",
"private": true
}
229 changes: 229 additions & 0 deletions apps/cli/test/config-package.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
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);
});
});
});
Loading