Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions apps/cli/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const dependencyVersionMap = {
"@planetscale/database": "^1.19.0",

"@libsql/client": "^0.14.0",
libsql: "^0.5.22",

"@neondatabase/serverless": "^1.0.1",
pg: "^8.14.1",
Expand Down
19 changes: 2 additions & 17 deletions apps/cli/src/helpers/core/create-readme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ function generateDatabaseSetup(
packageManagerRunCmd: string,
orm: ORM,
dbSetup: DatabaseSetup,
serverDeploy?: string,
_serverDeploy?: string,
backend?: string,
) {
if (database === "none") {
Expand All @@ -565,9 +565,7 @@ function generateDatabaseSetup(
1. Start the local SQLite database:
${
dbSetup === "d1"
? serverDeploy === "alchemy"
? "D1 local development and migrations are handled automatically by Alchemy during dev and deploy."
: "Local development for a Cloudflare D1 database will already be running as part of the `wrangler dev` command."
? "D1 local development and migrations are handled automatically by Alchemy during dev and deploy."
: `\`\`\`bash
cd ${dbLocalPath} && ${packageManagerRunCmd} db:local
\`\`\`
Expand Down Expand Up @@ -765,18 +763,5 @@ function generateDeploymentCommands(
}
}

if (webDeploy === "wrangler" || serverDeploy === "wrangler") {
lines.push("\n## Deployment (Cloudflare Wrangler)");
if (webDeploy === "wrangler") {
lines.push(`- Web deploy: cd apps/web && ${packageManagerRunCmd} deploy`);
}
if (serverDeploy === "wrangler") {
lines.push(
`- Server dev: cd apps/server && ${packageManagerRunCmd} dev`,
`- Server deploy: cd apps/server && ${packageManagerRunCmd} deploy`,
);
}
}

return lines.length ? `\n${lines.join("\n")}\n` : "";
}
8 changes: 6 additions & 2 deletions apps/cli/src/helpers/core/db-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export async function setupDatabase(

const s = spinner();
const dbPackageDir = path.join(projectDir, "packages/db");
const webDir = path.join(projectDir, "apps/web");

if (!(await fs.pathExists(dbPackageDir))) {
return;
Expand Down Expand Up @@ -64,7 +65,6 @@ export async function setupDatabase(
});
}

const webDir = path.join(projectDir, "apps/web");
if (await fs.pathExists(webDir)) {
await addPackageDependency({
dependencies: ["@prisma/client"],
Expand All @@ -74,10 +74,14 @@ export async function setupDatabase(
} else if (orm === "drizzle") {
if (database === "sqlite") {
await addPackageDependency({
dependencies: ["drizzle-orm", "@libsql/client"],
dependencies: ["drizzle-orm", "@libsql/client", "libsql"],
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify that both @libsql/client and libsql packages are required.

The dependency array now includes both @libsql/client and libsql. Please confirm whether both packages are necessary for libsql/Drizzle integration with SQLite, or if there's any redundancy.


🌐 Web query:

Do libsql Drizzle projects require both @libsql/client and libsql npm packages?

💡 Result:

Short answer: No — Drizzle projects using the libSQL driver only require the @libsql/client package (plus drizzle-orm/drizzle-kit as usual). The separate libsql npm package is a different libSQL JS binding and is not required by Drizzle. [1][2][3]

Sources:

  • Drizzle docs (libsql driver install instructions — recommends @libsql/client). [1]
  • @libsql/client npm page (client/SDK used by Drizzle). [2]
  • libsql npm page (separate libSQL Node bindings / different package). [3]

Remove the libsql package from dependencies — only @libsql/client is required.

Drizzle's libSQL driver only requires @libsql/client; the separate libsql package is an unrelated libSQL JS binding and should not be included in the dependencies array at line 77.

🤖 Prompt for AI Agents
In apps/cli/src/helpers/core/db-setup.ts around line 77, the dependencies array
incorrectly includes the standalone "libsql" package; remove "libsql" so the
array only lists "drizzle-orm" and "@libsql/client". Update the dependencies
entry to delete the "libsql" string and ensure no other references to the
removed package exist in this file.

devDependencies: ["drizzle-kit"],
projectDir: dbPackageDir,
});
await addPackageDependency({
dependencies: ["@libsql/client", "libsql"],
projectDir: webDir,
});
Copy link

Choose a reason for hiding this comment

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

Bug: Drizzle: Inconsistent Directory Checks

For Drizzle with SQLite, webDir dependencies are added without checking if the directory exists first. The Prisma section (line 85) checks if (await fs.pathExists(webDir)) before proceeding, but the Drizzle section skips this check, potentially causing errors if apps/web doesn't exist.

Fix in Cursor Fix in Web

} else if (database === "postgres") {
if (dbSetup === "neon") {
await addPackageDependency({
Expand Down
2 changes: 0 additions & 2 deletions apps/cli/src/helpers/core/env-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,6 @@ ${hasWeb ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}
case "sqlite":
if (
config.runtime === "workers" ||
webDeploy === "wrangler" ||
serverDeploy === "wrangler" ||
webDeploy === "alchemy" ||
serverDeploy === "alchemy"
) {
Expand Down
88 changes: 4 additions & 84 deletions apps/cli/src/helpers/core/post-installation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,6 @@ export async function displayPostInstallInstructions(
config.payments === "polar" && config.auth === "better-auth"
? getPolarInstructions(backend)
: "";
const wranglerDeployInstructions = getWranglerDeployInstructions(
runCmd,
webDeploy,
serverDeploy,
backend,
);
const alchemyDeployInstructions = getAlchemyDeployInstructions(
runCmd,
webDeploy,
Expand Down Expand Up @@ -127,10 +121,7 @@ export async function displayPostInstallInstructions(
if (
database === "sqlite" &&
dbSetup === "none" &&
(serverDeploy === "wrangler" ||
serverDeploy === "alchemy" ||
webDeploy === "wrangler" ||
webDeploy === "alchemy")
(serverDeploy === "alchemy" || webDeploy === "alchemy")
) {
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:local\n${pc.dim(
" (starts local SQLite server for Workers compatibility)",
Expand Down Expand Up @@ -162,9 +153,6 @@ export async function displayPostInstallInstructions(
)} Complete D1 database setup first\n (see Database commands below)\n`;
}
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
if (serverDeploy === "wrangler") {
output += `${pc.cyan(`${stepCounter++}.`)} cd apps/server && ${runCmd} cf-typegen\n`;
}
}
}

Expand Down Expand Up @@ -203,8 +191,6 @@ export async function displayPostInstallInstructions(
if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
if (wranglerDeployInstructions)
output += `\n${wranglerDeployInstructions.trim()}\n`;
if (alchemyDeployInstructions)
output += `\n${alchemyDeployInstructions.trim()}\n`;
if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
Expand Down Expand Up @@ -261,10 +247,10 @@ async function getDatabaseInstructions(
database: Database,
orm?: ORM,
runCmd?: string,
runtime?: Runtime,
_runtime?: Runtime,
dbSetup?: DatabaseSetup,
serverDeploy?: string,
backend?: string,
_backend?: string,
) {
const instructions: string[] = [];

Expand All @@ -277,49 +263,6 @@ async function getDatabaseInstructions(
}
}

if (serverDeploy === "wrangler" && dbSetup === "d1") {
if (orm === "prisma" && runtime === "workers") {
instructions.push(
`\n${pc.yellow(
"WARNING:",
)} Prisma + D1 on Workers with Wrangler has migration issues.\n Consider using Alchemy deploy instead of Wrangler for D1 projects.\n`,
);
}
const packageManager = runCmd === "npm run" ? "npm" : runCmd || "npm";

instructions.push(
`${pc.cyan("1.")} Login to Cloudflare: ${pc.white(
`${packageManager} wrangler login`,
)}`,
);
instructions.push(
`${pc.cyan("2.")} Create D1 database: ${pc.white(
`${packageManager} wrangler d1 create your-database-name`,
)}`,
);
const wranglerPath = backend === "self" ? "apps/web" : "apps/server";
instructions.push(
`${pc.cyan(
"3.",
)} Update ${wranglerPath}/wrangler.jsonc with database_id and database_name`,
);
instructions.push(
`${pc.cyan("4.")} Generate migrations: ${pc.white(
`cd ${wranglerPath} && ${runCmd} db:generate`,
)}`,
);
instructions.push(
`${pc.cyan("5.")} Apply migrations locally: ${pc.white(
`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME --local`,
)}`,
);
instructions.push(
`${pc.cyan("6.")} Apply migrations to production: ${pc.white(
`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME`,
)}`,
);
}

if (dbSetup === "d1" && serverDeploy === "alchemy") {
if (orm === "drizzle") {
instructions.push(
Expand Down Expand Up @@ -442,29 +385,6 @@ function getBunWebNativeWarning() {
)} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
}

function getWranglerDeployInstructions(
runCmd?: string,
webDeploy?: string,
serverDeploy?: string,
backend?: string,
) {
const instructions: string[] = [];

if (webDeploy === "wrangler") {
const deployPath = backend === "self" ? "apps/web" : "apps/web";
instructions.push(
`${pc.bold("Deploy web to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd ${deployPath} && ${runCmd} deploy`}`,
);
}
if (serverDeploy === "wrangler" && backend !== "self") {
instructions.push(
`${pc.bold("Deploy server to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} deploy`}`,
);
}

return instructions.length ? `\n${instructions.join("\n")}` : "";
}

function getClerkInstructions() {
return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Follow the guide: ${pc.underline("https://docs.convex.dev/auth/clerk")}\n${pc.cyan("•")} Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n${pc.cyan("•")} Set CLERK_PUBLISHABLE_KEY in apps/*/.env`;
}
Expand All @@ -485,7 +405,7 @@ function getAlchemyDeployInstructions(

if (webDeploy === "alchemy" && serverDeploy !== "alchemy") {
instructions.push(
`${pc.bold("Deploy web with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/web && ${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/web && ${runCmd} destroy`}`,
`${pc.bold("Deploy web with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/web && ${runCmd} alchemy dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/web && ${runCmd} destroy`}`,
Copy link

Choose a reason for hiding this comment

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

Bug: Inconsistent Alchemy Command Prefixes

Inconsistent alchemy command prefix in deployment instructions. The "Dev" command includes alchemy prefix (cd apps/web && ${runCmd} alchemy dev), but the "Deploy" and "Destroy" commands don't (cd apps/web && ${runCmd} deploy). This mismatch will cause the deploy and destroy commands to fail. All three should consistently use either just ${runCmd} deploy or ${runCmd} alchemy deploy.

Fix in Cursor Fix in Web

Copy link

Choose a reason for hiding this comment

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

Bug: Alchemy Web Deployment: Command Prefix Error

The web Alchemy deployment instructions include an incorrect Dev command prefix. For web-only Alchemy deployment, the instruction shows cd apps/web && ${runCmd} alchemy dev, but should be cd apps/web && ${runCmd} dev since the alchemy scripts are defined in the root package.json (as dev: "alchemy dev"). The current format attempts to run npm run alchemy dev which is incorrect.

Fix in Cursor Fix in Web

);
} else if (
serverDeploy === "alchemy" &&
Expand Down
8 changes: 6 additions & 2 deletions apps/cli/src/helpers/core/template-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,9 @@ export async function setupDeploymentTemplates(
context,
);

await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
if (!isBackendSelf) {
await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
}
}
} else {
if (context.webDeploy === "alchemy") {
Expand All @@ -1183,7 +1185,9 @@ export async function setupDeploymentTemplates(
context,
);

await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
if (!isBackendSelf) {
await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
}
}
}

Expand Down
32 changes: 1 addition & 31 deletions apps/cli/src/helpers/database-providers/d1-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,7 @@ import { addEnvVariablesToFile, type EnvVariable } from "../core/env-setup";
export async function setupCloudflareD1(config: ProjectConfig) {
const { projectDir, serverDeploy, orm, backend } = config;

if (serverDeploy === "wrangler") {
const targetApp = backend === "self" ? "apps/web" : "apps/server";
const envPath = path.join(projectDir, targetApp, ".env");

const variables: EnvVariable[] = [
{
key: "CLOUDFLARE_ACCOUNT_ID",
value: "",
condition: true,
},
{
key: "CLOUDFLARE_DATABASE_ID",
value: "",
condition: true,
},
{
key: "CLOUDFLARE_D1_TOKEN",
value: "",
condition: true,
},
];

try {
await addEnvVariablesToFile(envPath, variables);
} catch (_err) {}
}

if (
(serverDeploy === "wrangler" || serverDeploy === "alchemy") &&
orm === "prisma"
) {
if (serverDeploy === "alchemy" && orm === "prisma") {
Copy link

Choose a reason for hiding this comment

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

Bug: Hardcoded DB path breaks self backend.

The DATABASE_URL path is hardcoded to use "apps/server" but should use the targetApp2 variable that was already computed based on backend === "self". When using self backend, this creates an incorrect path pointing to apps/server/local.db instead of apps/web/local.db.

Fix in Cursor Fix in Web

const targetApp2 = backend === "self" ? "apps/web" : "apps/server";
const envPath = path.join(projectDir, targetApp2, ".env");
const variables: EnvVariable[] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export async function setupCombinedAlchemyDeploy(

const serverDir = path.join(projectDir, "apps/server");
if (await fs.pathExists(serverDir)) {
await setupAlchemyServerDeploy(serverDir, packageManager, projectDir);
await setupAlchemyServerDeploy(serverDir, projectDir);
}

const frontend = config.frontend;
Expand Down
67 changes: 3 additions & 64 deletions apps/cli/src/helpers/deployment/server-deploy-setup.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import path from "node:path";
import { log, spinner } from "@clack/prompts";
import { execa } from "execa";
import fs from "fs-extra";
import pc from "picocolors";
import type { PackageManager, ProjectConfig } from "../../types";
import type { ProjectConfig } from "../../types";
import { addPackageDependency } from "../../utils/add-package-deps";
import { getPackageExecutionCommand } from "../../utils/package-runner";

export async function setupServerDeploy(config: ProjectConfig) {
const { serverDeploy, webDeploy, projectDir } = config;
const { packageManager } = config;

if (serverDeploy === "none") return;

Expand All @@ -20,69 +15,13 @@ export async function setupServerDeploy(config: ProjectConfig) {
const serverDir = path.join(projectDir, "apps/server");
if (!(await fs.pathExists(serverDir))) return;

if (serverDeploy === "wrangler") {
await setupWorkersServerDeploy(serverDir, packageManager);
await generateCloudflareWorkerTypes({ serverDir, packageManager });
} else if (serverDeploy === "alchemy") {
await setupAlchemyServerDeploy(serverDir, packageManager, projectDir);
}
}

async function setupWorkersServerDeploy(
serverDir: string,
_packageManager: PackageManager,
) {
const packageJsonPath = path.join(serverDir, "package.json");
if (!(await fs.pathExists(packageJsonPath))) return;

const packageJson = await fs.readJson(packageJsonPath);

packageJson.scripts = {
...packageJson.scripts,
dev: "wrangler dev --port=3000",
start: "wrangler dev",
deploy: "wrangler deploy",
build: "wrangler deploy --dry-run",
"cf-typegen": "wrangler types --env-interface CloudflareBindings",
};

await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });

await addPackageDependency({
devDependencies: ["wrangler", "@types/node"],
projectDir: serverDir,
});
}

async function generateCloudflareWorkerTypes({
serverDir,
packageManager,
}: {
serverDir: string;
packageManager: ProjectConfig["packageManager"];
}) {
if (!(await fs.pathExists(serverDir))) return;
const s = spinner();
try {
s.start("Generating Cloudflare Workers types...");
const runCmd = getPackageExecutionCommand(
packageManager,
"wrangler types --env-interface CloudflareBindings",
);
await execa(runCmd, { cwd: serverDir, shell: true });
s.stop("Cloudflare Workers types generated successfully!");
} catch {
s.stop(pc.yellow("Failed to generate Cloudflare Workers types"));
const managerCmd = `${packageManager} run`;
log.warn(
`Note: You can manually run 'cd apps/server && ${managerCmd} cf-typegen' in the project directory later`,
);
if (serverDeploy === "alchemy") {
await setupAlchemyServerDeploy(serverDir, projectDir);
}
}

export async function setupAlchemyServerDeploy(
serverDir: string,
_packageManager: PackageManager,
projectDir?: string,
) {
if (!(await fs.pathExists(serverDir))) return;
Expand Down
Loading