mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat(cli): add react router support with alchemy (#542)
This commit is contained in:
@@ -138,7 +138,7 @@ export const dependencyVersionMap = {
|
||||
"@sveltejs/adapter-cloudflare": "^7.2.1",
|
||||
"@cloudflare/workers-types": "^4.20250822.0",
|
||||
|
||||
alchemy: "^0.62.1",
|
||||
alchemy: "^0.63.0",
|
||||
// temporary workaround for alchemy + tanstack start
|
||||
nitropack: "^2.12.4",
|
||||
|
||||
|
||||
@@ -76,11 +76,12 @@ export async function createProject(options: ProjectConfig) {
|
||||
|
||||
await handleExtras(projectDir, options);
|
||||
|
||||
await setupEnvironmentVariables(options);
|
||||
await updatePackageConfigurations(projectDir, options);
|
||||
|
||||
await setupWebDeploy(options);
|
||||
await setupServerDeploy(options);
|
||||
|
||||
await setupEnvironmentVariables(options);
|
||||
await updatePackageConfigurations(projectDir, options);
|
||||
await createReadme(projectDir, options);
|
||||
|
||||
await writeBtsConfig(options);
|
||||
|
||||
@@ -35,6 +35,8 @@ function generateReadmeContent(options: ProjectConfig): string {
|
||||
frontend = ["tanstack-router"],
|
||||
backend = "hono",
|
||||
api = "trpc",
|
||||
webDeploy,
|
||||
serverDeploy,
|
||||
} = options;
|
||||
|
||||
const isConvex = backend === "convex";
|
||||
@@ -103,6 +105,7 @@ Follow the prompts to create a new Convex project and connect it to your applica
|
||||
packageManagerRunCmd,
|
||||
orm,
|
||||
options.dbSetup,
|
||||
options.serverDeploy,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -120,6 +123,8 @@ ${
|
||||
: ""
|
||||
}
|
||||
|
||||
${generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeploy)}
|
||||
|
||||
## Project Structure
|
||||
|
||||
\`\`\`
|
||||
@@ -475,6 +480,7 @@ function generateDatabaseSetup(
|
||||
packageManagerRunCmd: string,
|
||||
orm: ORM,
|
||||
dbSetup: DatabaseSetup,
|
||||
serverDeploy?: string,
|
||||
): string {
|
||||
if (database === "none") {
|
||||
return "";
|
||||
@@ -494,7 +500,9 @@ function generateDatabaseSetup(
|
||||
1. Start the local SQLite database:
|
||||
${
|
||||
dbSetup === "d1"
|
||||
? "Local development for a Cloudflare D1 database will already be running as part of the `wrangler dev` command."
|
||||
? 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."
|
||||
: `\`\`\`bash
|
||||
cd apps/server && ${packageManagerRunCmd} db:local
|
||||
\`\`\`
|
||||
@@ -632,3 +640,51 @@ function generateScriptsList(
|
||||
|
||||
return scripts;
|
||||
}
|
||||
|
||||
function generateDeploymentCommands(
|
||||
packageManagerRunCmd: string,
|
||||
webDeploy?: string,
|
||||
serverDeploy?: string,
|
||||
): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
if (webDeploy === "alchemy" || serverDeploy === "alchemy") {
|
||||
lines.push("## Deployment (Alchemy)");
|
||||
if (webDeploy === "alchemy" && serverDeploy !== "alchemy") {
|
||||
lines.push(
|
||||
`- Web dev: cd apps/web && ${packageManagerRunCmd} dev`,
|
||||
`- Web deploy: cd apps/web && ${packageManagerRunCmd} deploy`,
|
||||
`- Web destroy: cd apps/web && ${packageManagerRunCmd} destroy`,
|
||||
);
|
||||
}
|
||||
if (serverDeploy === "alchemy" && webDeploy !== "alchemy") {
|
||||
lines.push(
|
||||
`- Server dev: cd apps/server && ${packageManagerRunCmd} dev`,
|
||||
`- Server deploy: cd apps/server && ${packageManagerRunCmd} deploy`,
|
||||
`- Server destroy: cd apps/server && ${packageManagerRunCmd} destroy`,
|
||||
);
|
||||
}
|
||||
if (webDeploy === "alchemy" && serverDeploy === "alchemy") {
|
||||
lines.push(
|
||||
`- Dev: ${packageManagerRunCmd} dev`,
|
||||
`- Deploy: ${packageManagerRunCmd} deploy`,
|
||||
`- Destroy: ${packageManagerRunCmd} destroy`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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` : "";
|
||||
}
|
||||
|
||||
@@ -412,15 +412,15 @@ function getAlchemyDeployInstructions(
|
||||
|
||||
if (webDeploy === "alchemy" && serverDeploy !== "alchemy") {
|
||||
instructions.push(
|
||||
`${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`}`,
|
||||
`${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`}`,
|
||||
);
|
||||
} else if (serverDeploy === "alchemy" && webDeploy !== "alchemy") {
|
||||
instructions.push(
|
||||
`${pc.bold("Deploy server with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/server && ${runCmd} alchemy:dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/server && ${runCmd} destroy`}`,
|
||||
`${pc.bold("Deploy server with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/server && ${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/server && ${runCmd} destroy`}`,
|
||||
);
|
||||
} else if (webDeploy === "alchemy" && serverDeploy === "alchemy") {
|
||||
instructions.push(
|
||||
`${pc.bold("Deploy with Alchemy:")}\n${pc.cyan("•")} Dev: ${`${runCmd} alchemy:dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`,
|
||||
`${pc.bold("Deploy with Alchemy:")}\n${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -835,12 +835,6 @@ export async function setupDeploymentTemplates(
|
||||
serverAppDir,
|
||||
context,
|
||||
);
|
||||
await processAndCopyFiles(
|
||||
"wrangler.jsonc.hbs",
|
||||
alchemyTemplateSrc,
|
||||
serverAppDir,
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -885,12 +879,6 @@ export async function setupDeploymentTemplates(
|
||||
serverAppDir,
|
||||
context,
|
||||
);
|
||||
await processAndCopyFiles(
|
||||
"wrangler.jsonc.hbs",
|
||||
alchemyTemplateSrc,
|
||||
serverAppDir,
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export async function setupCombinedAlchemyDeploy(
|
||||
...pkg.scripts,
|
||||
deploy: "alchemy deploy",
|
||||
destroy: "alchemy destroy",
|
||||
"alchemy:dev": "alchemy dev",
|
||||
dev: "alchemy dev",
|
||||
};
|
||||
await fs.writeJson(rootPkgPath, pkg, { spaces: 2 });
|
||||
}
|
||||
@@ -49,18 +49,32 @@ export async function setupCombinedAlchemyDeploy(
|
||||
const isSolid = frontend.includes("solid");
|
||||
|
||||
if (isNext) {
|
||||
await setupNextAlchemyDeploy(projectDir, packageManager);
|
||||
await setupNextAlchemyDeploy(projectDir, packageManager, {
|
||||
skipAppScripts: true,
|
||||
});
|
||||
} else if (isNuxt) {
|
||||
await setupNuxtAlchemyDeploy(projectDir, packageManager);
|
||||
await setupNuxtAlchemyDeploy(projectDir, packageManager, {
|
||||
skipAppScripts: true,
|
||||
});
|
||||
} else if (isSvelte) {
|
||||
await setupSvelteAlchemyDeploy(projectDir, packageManager);
|
||||
await setupSvelteAlchemyDeploy(projectDir, packageManager, {
|
||||
skipAppScripts: true,
|
||||
});
|
||||
} else if (isTanstackStart) {
|
||||
await setupTanStackStartAlchemyDeploy(projectDir, packageManager);
|
||||
await setupTanStackStartAlchemyDeploy(projectDir, packageManager, {
|
||||
skipAppScripts: true,
|
||||
});
|
||||
} else if (isTanstackRouter) {
|
||||
await setupTanStackRouterAlchemyDeploy(projectDir, packageManager);
|
||||
await setupTanStackRouterAlchemyDeploy(projectDir, packageManager, {
|
||||
skipAppScripts: true,
|
||||
});
|
||||
} else if (isReactRouter) {
|
||||
await setupReactRouterAlchemyDeploy(projectDir, packageManager);
|
||||
await setupReactRouterAlchemyDeploy(projectDir, packageManager, {
|
||||
skipAppScripts: true,
|
||||
});
|
||||
} else if (isSolid) {
|
||||
await setupSolidAlchemyDeploy(projectDir, packageManager);
|
||||
await setupSolidAlchemyDeploy(projectDir, packageManager, {
|
||||
skipAppScripts: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
export async function setupNextAlchemyDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
options?: { skipAppScripts?: boolean },
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
@@ -19,12 +20,14 @@ export async function setupNextAlchemyDeploy(
|
||||
if (await fs.pathExists(pkgPath)) {
|
||||
const pkg = await fs.readJson(pkgPath);
|
||||
|
||||
if (!options?.skipAppScripts) {
|
||||
pkg.scripts = {
|
||||
...pkg.scripts,
|
||||
deploy: "alchemy deploy",
|
||||
destroy: "alchemy destroy",
|
||||
"alchemy:dev": "alchemy dev",
|
||||
dev: "alchemy dev",
|
||||
};
|
||||
}
|
||||
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
export async function setupNuxtAlchemyDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
options?: { skipAppScripts?: boolean },
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
@@ -20,12 +21,14 @@ export async function setupNuxtAlchemyDeploy(
|
||||
if (await fs.pathExists(pkgPath)) {
|
||||
const pkg = await fs.readJson(pkgPath);
|
||||
|
||||
if (!options?.skipAppScripts) {
|
||||
pkg.scripts = {
|
||||
...pkg.scripts,
|
||||
deploy: "alchemy deploy",
|
||||
destroy: "alchemy destroy",
|
||||
"alchemy:dev": "alchemy dev",
|
||||
dev: "alchemy dev",
|
||||
};
|
||||
}
|
||||
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import { IndentationText, Node, Project, QuoteKind } from "ts-morph";
|
||||
import type { PackageManager } from "../../../types";
|
||||
import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
|
||||
export async function setupReactRouterAlchemyDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
options?: { skipAppScripts?: boolean },
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: ["alchemy", "@cloudflare/vite-plugin", "dotenv"],
|
||||
devDependencies: ["alchemy", "dotenv"],
|
||||
projectDir: webAppDir,
|
||||
});
|
||||
|
||||
@@ -20,149 +20,14 @@ export async function setupReactRouterAlchemyDeploy(
|
||||
if (await fs.pathExists(pkgPath)) {
|
||||
const pkg = await fs.readJson(pkgPath);
|
||||
|
||||
if (!options?.skipAppScripts) {
|
||||
pkg.scripts = {
|
||||
...pkg.scripts,
|
||||
deploy: "alchemy deploy",
|
||||
destroy: "alchemy destroy",
|
||||
"alchemy:dev": "alchemy dev",
|
||||
dev: "alchemy dev",
|
||||
};
|
||||
}
|
||||
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
||||
}
|
||||
|
||||
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
||||
if (await fs.pathExists(viteConfigPath)) {
|
||||
try {
|
||||
const project = new Project({
|
||||
manipulationSettings: {
|
||||
indentationText: IndentationText.TwoSpaces,
|
||||
quoteKind: QuoteKind.Double,
|
||||
},
|
||||
});
|
||||
|
||||
project.addSourceFileAtPath(viteConfigPath);
|
||||
const sourceFile = project.getSourceFileOrThrow(viteConfigPath);
|
||||
|
||||
const alchemyImport = sourceFile.getImportDeclaration(
|
||||
"alchemy/cloudflare/react-router",
|
||||
);
|
||||
if (!alchemyImport) {
|
||||
sourceFile.addImportDeclaration({
|
||||
moduleSpecifier: "alchemy/cloudflare/react-router",
|
||||
defaultImport: "alchemy",
|
||||
});
|
||||
}
|
||||
|
||||
const exportAssignment = sourceFile.getExportAssignment(
|
||||
(d) => !d.isExportEquals(),
|
||||
);
|
||||
if (!exportAssignment) return;
|
||||
|
||||
const defineConfigCall = exportAssignment.getExpression();
|
||||
if (
|
||||
!Node.isCallExpression(defineConfigCall) ||
|
||||
defineConfigCall.getExpression().getText() !== "defineConfig"
|
||||
)
|
||||
return;
|
||||
|
||||
let configObject = defineConfigCall.getArguments()[0];
|
||||
if (!configObject) {
|
||||
configObject = defineConfigCall.addArgument("{}");
|
||||
}
|
||||
|
||||
if (Node.isObjectLiteralExpression(configObject)) {
|
||||
const pluginsProperty = configObject.getProperty("plugins");
|
||||
if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
|
||||
const initializer = pluginsProperty.getInitializer();
|
||||
if (Node.isArrayLiteralExpression(initializer)) {
|
||||
const hasCloudflarePlugin = initializer
|
||||
.getElements()
|
||||
.some((el) => el.getText().includes("cloudflare("));
|
||||
|
||||
if (!hasCloudflarePlugin) {
|
||||
initializer.addElement("alchemy()");
|
||||
}
|
||||
}
|
||||
} else if (!pluginsProperty) {
|
||||
configObject.addPropertyAssignment({
|
||||
name: "plugins",
|
||||
initializer: "[alchemy()]",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await project.save();
|
||||
} catch (error) {
|
||||
console.warn("Failed to update vite.config.ts:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const reactRouterConfigPath = path.join(webAppDir, "react-router.config.ts");
|
||||
if (await fs.pathExists(reactRouterConfigPath)) {
|
||||
try {
|
||||
const project = new Project({
|
||||
manipulationSettings: {
|
||||
indentationText: IndentationText.TwoSpaces,
|
||||
quoteKind: QuoteKind.Double,
|
||||
},
|
||||
});
|
||||
|
||||
project.addSourceFileAtPath(reactRouterConfigPath);
|
||||
const sourceFile = project.getSourceFileOrThrow(reactRouterConfigPath);
|
||||
|
||||
const exportAssignment = sourceFile.getExportAssignment(
|
||||
(d) => !d.isExportEquals(),
|
||||
);
|
||||
if (!exportAssignment) return;
|
||||
|
||||
const configExpression = exportAssignment.getExpression();
|
||||
let configObject: Node | undefined;
|
||||
|
||||
if (Node.isObjectLiteralExpression(configExpression)) {
|
||||
configObject = configExpression;
|
||||
} else if (Node.isSatisfiesExpression(configExpression)) {
|
||||
const expression = configExpression.getExpression();
|
||||
if (Node.isObjectLiteralExpression(expression)) {
|
||||
configObject = expression;
|
||||
}
|
||||
}
|
||||
|
||||
if (!configObject || !Node.isObjectLiteralExpression(configObject))
|
||||
return;
|
||||
|
||||
const futureProperty = configObject.getProperty("future");
|
||||
|
||||
if (!futureProperty) {
|
||||
configObject.addPropertyAssignment({
|
||||
name: "future",
|
||||
initializer: `{
|
||||
unstable_viteEnvironmentApi: true,
|
||||
}`,
|
||||
});
|
||||
} else if (Node.isPropertyAssignment(futureProperty)) {
|
||||
const futureInitializer = futureProperty.getInitializer();
|
||||
|
||||
if (Node.isObjectLiteralExpression(futureInitializer)) {
|
||||
const viteEnvApiProp = futureInitializer.getProperty(
|
||||
"unstable_viteEnvironmentApi",
|
||||
);
|
||||
|
||||
if (!viteEnvApiProp) {
|
||||
futureInitializer.addPropertyAssignment({
|
||||
name: "unstable_viteEnvironmentApi",
|
||||
initializer: "true",
|
||||
});
|
||||
} else if (Node.isPropertyAssignment(viteEnvApiProp)) {
|
||||
const value = viteEnvApiProp.getInitializer()?.getText();
|
||||
if (value === "false") {
|
||||
viteEnvApiProp.setInitializer("true");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await project.save();
|
||||
} catch (error) {
|
||||
console.warn("Failed to update react-router.config.ts:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
export async function setupSolidAlchemyDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
options?: { skipAppScripts?: boolean },
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
@@ -19,12 +20,14 @@ export async function setupSolidAlchemyDeploy(
|
||||
if (await fs.pathExists(pkgPath)) {
|
||||
const pkg = await fs.readJson(pkgPath);
|
||||
|
||||
if (!options?.skipAppScripts) {
|
||||
pkg.scripts = {
|
||||
...pkg.scripts,
|
||||
deploy: "alchemy deploy",
|
||||
destroy: "alchemy destroy",
|
||||
"alchemy:dev": "alchemy dev",
|
||||
dev: "alchemy dev",
|
||||
};
|
||||
}
|
||||
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
export async function setupSvelteAlchemyDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
options?: { skipAppScripts?: boolean },
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
@@ -20,12 +21,15 @@ export async function setupSvelteAlchemyDeploy(
|
||||
if (await fs.pathExists(pkgPath)) {
|
||||
const pkg = await fs.readJson(pkgPath);
|
||||
|
||||
if (!options?.skipAppScripts) {
|
||||
pkg.scripts = {
|
||||
...pkg.scripts,
|
||||
deploy: "alchemy deploy",
|
||||
destroy: "alchemy destroy",
|
||||
"alchemy:dev": "alchemy dev",
|
||||
dev: "alchemy dev",
|
||||
};
|
||||
}
|
||||
|
||||
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
export async function setupTanStackRouterAlchemyDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
options?: { skipAppScripts?: boolean },
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
@@ -19,12 +20,15 @@ export async function setupTanStackRouterAlchemyDeploy(
|
||||
if (await fs.pathExists(pkgPath)) {
|
||||
const pkg = await fs.readJson(pkgPath);
|
||||
|
||||
if (!options?.skipAppScripts) {
|
||||
pkg.scripts = {
|
||||
...pkg.scripts,
|
||||
deploy: "alchemy deploy",
|
||||
destroy: "alchemy destroy",
|
||||
"alchemy:dev": "alchemy dev",
|
||||
dev: "alchemy dev",
|
||||
};
|
||||
}
|
||||
|
||||
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
export async function setupTanStackStartAlchemyDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
options?: { skipAppScripts?: boolean },
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
@@ -20,12 +21,15 @@ export async function setupTanStackStartAlchemyDeploy(
|
||||
if (await fs.pathExists(pkgPath)) {
|
||||
const pkg = await fs.readJson(pkgPath);
|
||||
|
||||
if (!options?.skipAppScripts) {
|
||||
pkg.scripts = {
|
||||
...pkg.scripts,
|
||||
deploy: "alchemy deploy",
|
||||
destroy: "alchemy destroy",
|
||||
"alchemy:dev": "alchemy dev",
|
||||
dev: "alchemy dev",
|
||||
};
|
||||
}
|
||||
|
||||
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
||||
}
|
||||
|
||||
|
||||
@@ -99,11 +99,9 @@ export async function setupAlchemyServerDeploy(
|
||||
|
||||
packageJson.scripts = {
|
||||
...packageJson.scripts,
|
||||
dev: "wrangler dev --port=3000",
|
||||
build: "wrangler deploy --dry-run",
|
||||
dev: "alchemy dev",
|
||||
deploy: "alchemy deploy",
|
||||
destroy: "alchemy destroy",
|
||||
"alchemy:dev": "alchemy dev",
|
||||
};
|
||||
|
||||
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||
|
||||
@@ -47,9 +47,7 @@ export async function getDeploymentChoice(
|
||||
return "none";
|
||||
}
|
||||
|
||||
const hasIncompatibleFrontend = frontend.some(
|
||||
(f) => f === "next" || f === "react-router",
|
||||
);
|
||||
const hasIncompatibleFrontend = frontend.some((f) => f === "next");
|
||||
const availableDeployments = hasIncompatibleFrontend
|
||||
? ["wrangler", "none"]
|
||||
: ["wrangler", "alchemy", "none"];
|
||||
@@ -84,9 +82,7 @@ export async function getDeploymentToAdd(
|
||||
return "none";
|
||||
}
|
||||
|
||||
const hasIncompatibleFrontend = frontend.some(
|
||||
(f) => f === "next" || f === "react-router",
|
||||
);
|
||||
const hasIncompatibleFrontend = frontend.some((f) => f === "next");
|
||||
|
||||
const options: DeploymentOption[] = [];
|
||||
|
||||
|
||||
@@ -324,9 +324,7 @@ export function validateAlchemyCompatibility(
|
||||
const isAlchemyServerDeploy = serverDeploy === "alchemy";
|
||||
|
||||
if (isAlchemyWebDeploy || isAlchemyServerDeploy) {
|
||||
const incompatibleFrontends = frontends.filter(
|
||||
(f) => f === "next" || f === "react-router",
|
||||
);
|
||||
const incompatibleFrontends = frontends.filter((f) => f === "next");
|
||||
|
||||
if (incompatibleFrontends.length > 0) {
|
||||
const deployType =
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Vite } from "alchemy/cloudflare";
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if (eq serverDeploy "alchemy")}}
|
||||
import { Worker, WranglerJson } from "alchemy/cloudflare";
|
||||
import { Worker } from "alchemy/cloudflare";
|
||||
{{#if (eq dbSetup "d1")}}
|
||||
import { D1Database } from "alchemy/cloudflare";
|
||||
{{/if}}
|
||||
@@ -44,7 +44,6 @@ await Exec("db-generate", {
|
||||
});
|
||||
|
||||
const db = await D1Database("database", {
|
||||
name: `${app.name}-${app.stage}-db`,
|
||||
migrationsDir: "apps/server/src/db/migrations",
|
||||
});
|
||||
{{/if}}
|
||||
@@ -53,7 +52,6 @@ const db = await D1Database("database", {
|
||||
{{#if (includes frontend "next")}}
|
||||
export const web = await Next("web", {
|
||||
{{#if (eq serverDeploy "alchemy")}}cwd: "apps/web",{{/if}}
|
||||
name: `${app.name}-${app.stage}-web`,
|
||||
bindings: {
|
||||
{{#if (eq backend "convex")}}
|
||||
NEXT_PUBLIC_CONVEX_URL: process.env.NEXT_PUBLIC_CONVEX_URL || "",
|
||||
@@ -68,7 +66,6 @@ export const web = await Next("web", {
|
||||
{{else if (includes frontend "nuxt")}}
|
||||
export const web = await Nuxt("web", {
|
||||
{{#if (eq serverDeploy "alchemy")}}cwd: "apps/web",{{/if}}
|
||||
name: `${app.name}-${app.stage}-web`,
|
||||
bindings: {
|
||||
{{#if (eq backend "convex")}}
|
||||
NUXT_PUBLIC_CONVEX_URL: process.env.NUXT_PUBLIC_CONVEX_URL || "",
|
||||
@@ -83,7 +80,6 @@ export const web = await Nuxt("web", {
|
||||
{{else if (includes frontend "svelte")}}
|
||||
export const web = await SvelteKit("web", {
|
||||
{{#if (eq serverDeploy "alchemy")}}cwd: "apps/web",{{/if}}
|
||||
name: `${app.name}-${app.stage}-web`,
|
||||
bindings: {
|
||||
{{#if (eq backend "convex")}}
|
||||
PUBLIC_CONVEX_URL: process.env.PUBLIC_CONVEX_URL || "",
|
||||
@@ -98,7 +94,6 @@ export const web = await SvelteKit("web", {
|
||||
{{else if (includes frontend "tanstack-start")}}
|
||||
export const web = await TanStackStart("web", {
|
||||
{{#if (eq serverDeploy "alchemy")}}cwd: "apps/web",{{/if}}
|
||||
name: `${app.name}-${app.stage}-web`,
|
||||
bindings: {
|
||||
{{#if (eq backend "convex")}}
|
||||
VITE_CONVEX_URL: process.env.VITE_CONVEX_URL || "",
|
||||
@@ -113,7 +108,6 @@ export const web = await TanStackStart("web", {
|
||||
{{else if (includes frontend "tanstack-router")}}
|
||||
export const web = await Vite("web", {
|
||||
{{#if (eq serverDeploy "alchemy")}}cwd: "apps/web",{{/if}}
|
||||
name: `${app.name}-${app.stage}-web`,
|
||||
assets: "dist",
|
||||
bindings: {
|
||||
{{#if (eq backend "convex")}}
|
||||
@@ -129,7 +123,6 @@ export const web = await Vite("web", {
|
||||
{{else if (includes frontend "react-router")}}
|
||||
export const web = await ReactRouter("web", {
|
||||
{{#if (eq serverDeploy "alchemy")}}cwd: "apps/web",{{/if}}
|
||||
name: `${app.name}-${app.stage}-web`,
|
||||
bindings: {
|
||||
{{#if (eq backend "convex")}}
|
||||
VITE_CONVEX_URL: process.env.VITE_CONVEX_URL || "",
|
||||
@@ -144,7 +137,6 @@ export const web = await ReactRouter("web", {
|
||||
{{else if (includes frontend "solid")}}
|
||||
export const web = await Vite("web", {
|
||||
{{#if (eq serverDeploy "alchemy")}}cwd: "apps/web",{{/if}}
|
||||
name: `${app.name}-${app.stage}-web`,
|
||||
assets: "dist",
|
||||
bindings: {
|
||||
{{#if (eq backend "convex")}}
|
||||
@@ -163,7 +155,6 @@ export const web = await Vite("web", {
|
||||
{{#if (eq serverDeploy "alchemy")}}
|
||||
export const server = await Worker("server", {
|
||||
{{#if (eq webDeploy "alchemy")}}cwd: "apps/server",{{/if}}
|
||||
name: `${app.name}-${app.stage}`,
|
||||
entrypoint: "src/index.ts",
|
||||
compatibility: "node",
|
||||
bindings: {
|
||||
@@ -188,14 +179,8 @@ export const server = await Worker("server", {
|
||||
port: 3000,
|
||||
},
|
||||
});
|
||||
|
||||
await WranglerJson("wrangler", {
|
||||
worker: server,
|
||||
});
|
||||
{{/if}}
|
||||
|
||||
|
||||
|
||||
{{#if (and (eq webDeploy "alchemy") (eq serverDeploy "alchemy"))}}
|
||||
console.log(`Web -> ${web.url}`);
|
||||
console.log(`Server -> ${server.url}`);
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// This is a temporary wrangler.jsonc file that will be overwritten by alchemy
|
||||
// It's only here so that `wrangler dev` can work or use alchemy dev instead
|
||||
{
|
||||
"name": "{{projectName}}",
|
||||
"main": "src/index.ts",
|
||||
"compatibility_date": "2025-08-16",
|
||||
"compatibility_flags": [
|
||||
"nodejs_compat",
|
||||
"nodejs_compat_populate_process_env"
|
||||
]
|
||||
}
|
||||
@@ -2804,27 +2804,8 @@ describe("create-better-t-stack smoke", () => {
|
||||
consola.success(`${dirName} built successfully`);
|
||||
}
|
||||
|
||||
if (scripts["check-types"]) {
|
||||
consola.start(`Type checking ${dirName}...`);
|
||||
const typeRes = await runScript(
|
||||
pm,
|
||||
projectDir,
|
||||
"check-types",
|
||||
[],
|
||||
120_000,
|
||||
);
|
||||
expect(typeRes.exitCode).toBe(0);
|
||||
consola.success(`${dirName} type check passed`);
|
||||
}
|
||||
|
||||
if (!scripts.build && !scripts["check-types"]) {
|
||||
consola.info(
|
||||
`No build or check-types script for ${dirName}, skipping`,
|
||||
);
|
||||
} else if (!scripts.build && scripts["check-types"]) {
|
||||
consola.info(
|
||||
`Only check-types script available for ${dirName}, type checking will be performed`,
|
||||
);
|
||||
if (!scripts.build) {
|
||||
consola.info(`No build script for ${dirName}, skipping`);
|
||||
}
|
||||
} catch (error) {
|
||||
consola.error(`${dirName} failed`, error);
|
||||
|
||||
@@ -1005,7 +1005,7 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
|
||||
if (isAlchemyWebDeploy || isAlchemyServerDeploy) {
|
||||
const incompatibleFrontends = nextStack.webFrontend.filter(
|
||||
(f) => f === "next" || f === "react-router",
|
||||
(f) => f === "next",
|
||||
);
|
||||
|
||||
if (incompatibleFrontends.length > 0) {
|
||||
@@ -1029,9 +1029,7 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
notes.webDeploy.hasIssue = true;
|
||||
notes.serverDeploy.hasIssue = true;
|
||||
|
||||
nextStack.webFrontend = nextStack.webFrontend.filter(
|
||||
(f) => f !== "next" && f !== "react-router",
|
||||
);
|
||||
nextStack.webFrontend = nextStack.webFrontend.filter((f) => f !== "next");
|
||||
|
||||
if (nextStack.webFrontend.length === 0) {
|
||||
nextStack.webFrontend = ["tanstack-router"];
|
||||
@@ -1632,10 +1630,7 @@ const StackBuilder = () => {
|
||||
const { adjustedStack } = analyzeStackCompatibility(simulatedStack);
|
||||
const finalStack = adjustedStack ?? simulatedStack;
|
||||
|
||||
if (
|
||||
category === "webFrontend" &&
|
||||
(optionId === "next" || optionId === "react-router")
|
||||
) {
|
||||
if (category === "webFrontend" && optionId === "next") {
|
||||
const isAlchemyWebDeploy = finalStack.webDeploy === "alchemy";
|
||||
const isAlchemyServerDeploy = finalStack.serverDeploy === "alchemy";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user