feat(cli): add alchemy and improve cli tooling and structure (#520)

This commit is contained in:
Aman Varshney
2025-08-20 23:43:58 +05:30
committed by GitHub
parent c5430ae4fd
commit 5788876c47
152 changed files with 5804 additions and 2264 deletions

View File

@@ -70,6 +70,7 @@ const CATEGORY_ORDER: Array<keyof typeof TECH_OPTIONS> = [
"orm",
"dbSetup",
"webDeploy",
"serverDeploy",
"auth",
"packageManager",
"addons",
@@ -122,6 +123,7 @@ const getBadgeColors = (category: string): string => {
return "border-orange-300 bg-orange-100 text-orange-800 dark:border-orange-700/30 dark:bg-orange-900/30 dark:text-orange-300";
case "git":
case "webDeploy":
case "serverDeploy":
case "install":
return "border-gray-300 bg-gray-100 text-gray-700 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400";
default:
@@ -716,10 +718,10 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
if (!isPWACompat && nextStack.addons.includes("pwa")) {
incompatibleAddons.push("pwa");
notes.webFrontend.notes.push(
"PWA addon requires TanStack/React Router or Solid. Addon will be removed.",
"PWA addon requires TanStack Router, React Router, Solid, or Next.js. Addon will be removed.",
);
notes.addons.notes.push(
"PWA requires TanStack/React Router/Solid. It will be removed.",
"PWA requires TanStack Router, React Router, Solid, or Next.js. It will be removed.",
);
notes.webFrontend.hasIssue = true;
notes.addons.hasIssue = true;
@@ -731,10 +733,10 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
if (!isTauriCompat && nextStack.addons.includes("tauri")) {
incompatibleAddons.push("tauri");
notes.webFrontend.notes.push(
"Tauri addon requires TanStack/React Router, Nuxt, Svelte, Solid, or Next.js. Addon will be removed.",
"Tauri addon requires TanStack Router, React Router, Nuxt, Svelte, Solid, or Next.js. Addon will be removed.",
);
notes.addons.notes.push(
"Tauri requires TanStack/React Router/Nuxt/Svelte/Solid/Next.js. It will be removed.",
"Tauri requires TanStack Router, React Router, Nuxt, Svelte, Solid, or Next.js. It will be removed.",
);
notes.webFrontend.hasIssue = true;
notes.addons.hasIssue = true;
@@ -881,6 +883,160 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
});
}
// Server deployment requires a backend (and not Convex)
if (
nextStack.serverDeploy !== "none" &&
(nextStack.backend === "none" || nextStack.backend === "convex")
) {
notes.serverDeploy.notes.push(
"Server deployment requires a supported backend. It will be disabled.",
);
notes.backend.notes.push(
"No compatible backend selected: Server deployment has been disabled.",
);
notes.serverDeploy.hasIssue = true;
notes.backend.hasIssue = true;
nextStack.serverDeploy = "none";
changed = true;
changes.push({
category: "serverDeploy",
message: "Server deployment set to 'none' (requires backend)",
});
}
// Cloudflare server deployments (wrangler/alchemy) require Workers runtime
if (nextStack.serverDeploy !== "none" && nextStack.runtime !== "workers") {
notes.serverDeploy.notes.push(
"Selected server deployment targets Cloudflare Workers. Runtime will be set to 'Cloudflare Workers'.",
);
notes.runtime.notes.push(
"Server deployment requires Cloudflare Workers runtime. It will be selected.",
);
notes.serverDeploy.hasIssue = true;
notes.runtime.hasIssue = true;
nextStack.runtime = "workers";
changed = true;
changes.push({
category: "serverDeploy",
message:
"Runtime set to 'Cloudflare Workers' (required by server deployment)",
});
// Apply Workers runtime compatibility adjustments
if (nextStack.backend !== "hono") {
notes.runtime.notes.push(
"Cloudflare Workers runtime requires Hono backend. Hono will be selected.",
);
notes.backend.notes.push(
"Cloudflare Workers runtime requires Hono backend. It will be selected.",
);
notes.runtime.hasIssue = true;
notes.backend.hasIssue = true;
nextStack.backend = "hono";
changes.push({
category: "runtime",
message: "Backend set to 'Hono' (required by Cloudflare Workers)",
});
}
if (nextStack.orm !== "drizzle" && nextStack.orm !== "none") {
notes.runtime.notes.push(
"Cloudflare Workers runtime requires Drizzle ORM or no ORM. Drizzle will be selected.",
);
notes.orm.notes.push(
"Cloudflare Workers runtime requires Drizzle ORM or no ORM. Drizzle will be selected.",
);
notes.runtime.hasIssue = true;
notes.orm.hasIssue = true;
nextStack.orm = "drizzle";
changes.push({
category: "runtime",
message: "ORM set to 'Drizzle' (required by Cloudflare Workers)",
});
}
if (nextStack.database === "mongodb") {
notes.runtime.notes.push(
"Cloudflare Workers runtime is not compatible with MongoDB. SQLite will be selected.",
);
notes.database.notes.push(
"MongoDB is not compatible with Cloudflare Workers runtime. SQLite will be selected.",
);
notes.runtime.hasIssue = true;
notes.database.hasIssue = true;
nextStack.database = "sqlite";
changes.push({
category: "runtime",
message:
"Database set to 'SQLite' (MongoDB not compatible with Workers)",
});
}
if (nextStack.dbSetup === "docker") {
notes.runtime.notes.push(
"Cloudflare Workers runtime does not support Docker setup. D1 will be selected.",
);
notes.dbSetup.notes.push(
"Docker setup is not compatible with Cloudflare Workers runtime. D1 will be selected.",
);
notes.runtime.hasIssue = true;
notes.dbSetup.hasIssue = true;
nextStack.dbSetup = "d1";
changes.push({
category: "runtime",
message: "DB Setup set to 'D1' (Docker not compatible with Workers)",
});
}
}
// Alchemy deployment validation - temporarily not compatible with Next.js and React Router
const isAlchemyWebDeploy = nextStack.webDeploy === "alchemy";
const isAlchemyServerDeploy = nextStack.serverDeploy === "alchemy";
if (isAlchemyWebDeploy || isAlchemyServerDeploy) {
const incompatibleFrontends = nextStack.webFrontend.filter(
(f) => f === "next" || f === "react-router",
);
if (incompatibleFrontends.length > 0) {
const deployType =
isAlchemyWebDeploy && isAlchemyServerDeploy
? "web and server deployment"
: isAlchemyWebDeploy
? "web deployment"
: "server deployment";
notes.webFrontend.notes.push(
`Alchemy ${deployType} is temporarily not compatible with ${incompatibleFrontends.join(" and ")}. These frontends will be removed.`,
);
notes.webDeploy.notes.push(
`Alchemy ${deployType} is temporarily not compatible with ${incompatibleFrontends.join(" and ")}.`,
);
notes.serverDeploy.notes.push(
`Alchemy ${deployType} is temporarily not compatible with ${incompatibleFrontends.join(" and ")}.`,
);
notes.webFrontend.hasIssue = true;
notes.webDeploy.hasIssue = true;
notes.serverDeploy.hasIssue = true;
// Remove incompatible frontends
nextStack.webFrontend = nextStack.webFrontend.filter(
(f) => f !== "next" && f !== "react-router",
);
// If no web frontends remain, set to default
if (nextStack.webFrontend.length === 0) {
nextStack.webFrontend = ["tanstack-router"];
}
changed = true;
changes.push({
category: "alchemy",
message: `Removed ${incompatibleFrontends.join(" and ")} (not compatible with Alchemy ${deployType})`,
});
}
}
return {
adjustedStack: changed ? nextStack : null,
notes,
@@ -985,6 +1141,13 @@ const generateCommand = (stackState: StackState): string => {
flags.push(`--web-deploy ${stackState.webDeploy}`);
}
if (
stackState.serverDeploy &&
!checkDefault("serverDeploy", stackState.serverDeploy)
) {
flags.push(`--server-deploy ${stackState.serverDeploy}`);
}
if (!checkDefault("install", stackState.install)) {
if (stackState.install === "false" && DEFAULT_STACK.install === "true") {
flags.push("--no-install");
@@ -1461,6 +1624,19 @@ const StackBuilder = () => {
const { adjustedStack } = analyzeStackCompatibility(simulatedStack);
const finalStack = adjustedStack ?? simulatedStack;
// Additional check for Alchemy compatibility with Next.js and React Router
if (
category === "webFrontend" &&
(optionId === "next" || optionId === "react-router")
) {
const isAlchemyWebDeploy = finalStack.webDeploy === "alchemy";
const isAlchemyServerDeploy = finalStack.serverDeploy === "alchemy";
if (isAlchemyWebDeploy || isAlchemyServerDeploy) {
return false;
}
}
if (
category === "webFrontend" ||
category === "nativeFrontend" ||