Files
create-better-t-stack/apps/cli/src/helpers/deployment/alchemy/alchemy-tanstack-start-setup.ts

159 lines
4.4 KiB
TypeScript

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 setupTanStackStartAlchemyDeploy(
projectDir: string,
_packageManager: PackageManager,
) {
const webAppDir = path.join(projectDir, "apps/web");
if (!(await fs.pathExists(webAppDir))) return;
await addPackageDependency({
devDependencies: ["alchemy", "nitropack", "dotenv"],
projectDir: webAppDir,
});
const pkgPath = path.join(webAppDir, "package.json");
if (await fs.pathExists(pkgPath)) {
const pkg = await fs.readJson(pkgPath);
pkg.scripts = {
...pkg.scripts,
deploy: "alchemy deploy",
destroy: "alchemy destroy",
"alchemy: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/tanstack-start",
);
if (!alchemyImport) {
sourceFile.addImportDeclaration({
moduleSpecifier: "alchemy/cloudflare/tanstack-start",
defaultImport: "alchemy",
});
} else {
alchemyImport.setModuleSpecifier("alchemy/cloudflare/tanstack-start");
}
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)) {
if (!configObject.getProperty("build")) {
configObject.addPropertyAssignment({
name: "build",
initializer: `{
target: "esnext",
rollupOptions: {
external: ["node:async_hooks", "cloudflare:workers"],
},
}`,
});
}
const pluginsProperty = configObject.getProperty("plugins");
if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
const initializer = pluginsProperty.getInitializer();
if (Node.isArrayLiteralExpression(initializer)) {
const hasShim = initializer
.getElements()
.some((el) => el.getText().includes("alchemy"));
if (!hasShim) {
initializer.addElement("alchemy()");
}
const tanstackElements = initializer
.getElements()
.filter((el) => el.getText().includes("tanstackStart"));
tanstackElements.forEach((element) => {
if (Node.isCallExpression(element)) {
const args = element.getArguments();
if (args.length === 0) {
element.addArgument(`{
target: "cloudflare-module",
customViteReactPlugin: true,
}`);
} else if (
args.length === 1 &&
Node.isObjectLiteralExpression(args[0])
) {
const configObj = args[0];
if (!configObj.getProperty("target")) {
configObj.addPropertyAssignment({
name: "target",
initializer: '"cloudflare-module"',
});
}
if (!configObj.getProperty("customViteReactPlugin")) {
configObj.addPropertyAssignment({
name: "customViteReactPlugin",
initializer: "true",
});
}
}
}
});
}
} else {
configObject.addPropertyAssignment({
name: "plugins",
initializer: "[alchemy()]",
});
}
}
await project.save();
} catch (error) {
console.warn("Failed to update vite.config.ts:", error);
}
}
// workaround for tanstack start + workers
const nitroConfigPath = path.join(webAppDir, "nitro.config.ts");
const nitroConfigContent = `import { defineNitroConfig } from "nitropack/config";
export default defineNitroConfig({
preset: "cloudflare-module",
cloudflare: {
nodeCompat: true,
},
});
`;
await fs.writeFile(nitroConfigPath, nitroConfigContent, "utf-8");
}