mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat(cli): add alchemy and improve cli tooling and structure (#520)
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import type { PackageManager, ProjectConfig } from "../../../types";
|
||||
import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
import { setupAlchemyServerDeploy } from "../server-deploy-setup";
|
||||
import { setupNextAlchemyDeploy } from "./alchemy-next-setup";
|
||||
import { setupNuxtAlchemyDeploy } from "./alchemy-nuxt-setup";
|
||||
import { setupReactRouterAlchemyDeploy } from "./alchemy-react-router-setup";
|
||||
import { setupSolidAlchemyDeploy } from "./alchemy-solid-setup";
|
||||
import { setupSvelteAlchemyDeploy } from "./alchemy-svelte-setup";
|
||||
import { setupTanStackRouterAlchemyDeploy } from "./alchemy-tanstack-router-setup";
|
||||
import { setupTanStackStartAlchemyDeploy } from "./alchemy-tanstack-start-setup";
|
||||
|
||||
export async function setupCombinedAlchemyDeploy(
|
||||
projectDir: string,
|
||||
packageManager: PackageManager,
|
||||
config: ProjectConfig,
|
||||
) {
|
||||
await addPackageDependency({
|
||||
devDependencies: ["alchemy", "dotenv"],
|
||||
projectDir,
|
||||
});
|
||||
|
||||
const rootPkgPath = path.join(projectDir, "package.json");
|
||||
if (await fs.pathExists(rootPkgPath)) {
|
||||
const pkg = await fs.readJson(rootPkgPath);
|
||||
|
||||
pkg.scripts = {
|
||||
...pkg.scripts,
|
||||
deploy: "alchemy deploy",
|
||||
destroy: "alchemy destroy",
|
||||
"alchemy:dev": "alchemy dev",
|
||||
};
|
||||
await fs.writeJson(rootPkgPath, pkg, { spaces: 2 });
|
||||
}
|
||||
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
if (await fs.pathExists(serverDir)) {
|
||||
await setupAlchemyServerDeploy(serverDir, packageManager);
|
||||
}
|
||||
|
||||
const frontend = config.frontend;
|
||||
const isNext = frontend.includes("next");
|
||||
const isNuxt = frontend.includes("nuxt");
|
||||
const isSvelte = frontend.includes("svelte");
|
||||
const isTanstackRouter = frontend.includes("tanstack-router");
|
||||
const isTanstackStart = frontend.includes("tanstack-start");
|
||||
const isReactRouter = frontend.includes("react-router");
|
||||
const isSolid = frontend.includes("solid");
|
||||
|
||||
if (isNext) {
|
||||
await setupNextAlchemyDeploy(projectDir, packageManager);
|
||||
} else if (isNuxt) {
|
||||
await setupNuxtAlchemyDeploy(projectDir, packageManager);
|
||||
} else if (isSvelte) {
|
||||
await setupSvelteAlchemyDeploy(projectDir, packageManager);
|
||||
} else if (isTanstackStart) {
|
||||
await setupTanStackStartAlchemyDeploy(projectDir, packageManager);
|
||||
} else if (isTanstackRouter) {
|
||||
await setupTanStackRouterAlchemyDeploy(projectDir, packageManager);
|
||||
} else if (isReactRouter) {
|
||||
await setupReactRouterAlchemyDeploy(projectDir, packageManager);
|
||||
} else if (isSolid) {
|
||||
await setupSolidAlchemyDeploy(projectDir, packageManager);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import type { PackageManager } from "../../../types";
|
||||
import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
|
||||
export async function setupNextAlchemyDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: ["alchemy", "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 });
|
||||
}
|
||||
}
|
||||
104
apps/cli/src/helpers/deployment/alchemy/alchemy-nuxt-setup.ts
Normal file
104
apps/cli/src/helpers/deployment/alchemy/alchemy-nuxt-setup.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
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 setupNuxtAlchemyDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: ["alchemy", "nitro-cloudflare-dev", "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 nuxtConfigPath = path.join(webAppDir, "nuxt.config.ts");
|
||||
if (!(await fs.pathExists(nuxtConfigPath))) return;
|
||||
|
||||
try {
|
||||
const project = new Project({
|
||||
manipulationSettings: {
|
||||
indentationText: IndentationText.TwoSpaces,
|
||||
quoteKind: QuoteKind.Double,
|
||||
},
|
||||
});
|
||||
|
||||
project.addSourceFileAtPath(nuxtConfigPath);
|
||||
const sourceFile = project.getSourceFileOrThrow(nuxtConfigPath);
|
||||
|
||||
const exportAssignment = sourceFile.getExportAssignment(
|
||||
(d) => !d.isExportEquals(),
|
||||
);
|
||||
if (!exportAssignment) return;
|
||||
|
||||
const defineConfigCall = exportAssignment.getExpression();
|
||||
if (
|
||||
!Node.isCallExpression(defineConfigCall) ||
|
||||
defineConfigCall.getExpression().getText() !== "defineNuxtConfig"
|
||||
)
|
||||
return;
|
||||
|
||||
let configObject = defineConfigCall.getArguments()[0];
|
||||
if (!configObject) {
|
||||
configObject = defineConfigCall.addArgument("{}");
|
||||
}
|
||||
|
||||
if (Node.isObjectLiteralExpression(configObject)) {
|
||||
if (!configObject.getProperty("nitro")) {
|
||||
configObject.addPropertyAssignment({
|
||||
name: "nitro",
|
||||
initializer: `{
|
||||
preset: "cloudflare_module",
|
||||
cloudflare: {
|
||||
deployConfig: true,
|
||||
nodeCompat: true
|
||||
}
|
||||
}`,
|
||||
});
|
||||
}
|
||||
|
||||
const modulesProperty = configObject.getProperty("modules");
|
||||
if (modulesProperty && Node.isPropertyAssignment(modulesProperty)) {
|
||||
const initializer = modulesProperty.getInitializer();
|
||||
if (Node.isArrayLiteralExpression(initializer)) {
|
||||
const hasModule = initializer
|
||||
.getElements()
|
||||
.some(
|
||||
(el) =>
|
||||
el.getText() === '"nitro-cloudflare-dev"' ||
|
||||
el.getText() === "'nitro-cloudflare-dev'",
|
||||
);
|
||||
if (!hasModule) {
|
||||
initializer.addElement('"nitro-cloudflare-dev"');
|
||||
}
|
||||
}
|
||||
} else if (!modulesProperty) {
|
||||
configObject.addPropertyAssignment({
|
||||
name: "modules",
|
||||
initializer: '["nitro-cloudflare-dev"]',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await project.save();
|
||||
} catch (error) {
|
||||
console.warn("Failed to update nuxt.config.ts:", error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
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,
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: ["alchemy", "@cloudflare/vite-plugin", "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/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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import type { PackageManager } from "../../../types";
|
||||
import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
|
||||
export async function setupSolidAlchemyDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: ["alchemy", "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 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
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 setupSvelteAlchemyDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: ["alchemy", "@sveltejs/adapter-cloudflare", "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 svelteConfigPath = path.join(webAppDir, "svelte.config.js");
|
||||
if (!(await fs.pathExists(svelteConfigPath))) return;
|
||||
|
||||
try {
|
||||
const project = new Project({
|
||||
manipulationSettings: {
|
||||
indentationText: IndentationText.TwoSpaces,
|
||||
quoteKind: QuoteKind.Single,
|
||||
},
|
||||
});
|
||||
|
||||
project.addSourceFileAtPath(svelteConfigPath);
|
||||
const sourceFile = project.getSourceFileOrThrow(svelteConfigPath);
|
||||
|
||||
const importDeclarations = sourceFile.getImportDeclarations();
|
||||
const adapterImport = importDeclarations.find((imp) =>
|
||||
imp.getModuleSpecifierValue().includes("@sveltejs/adapter"),
|
||||
);
|
||||
|
||||
if (adapterImport) {
|
||||
adapterImport.setModuleSpecifier("alchemy/cloudflare/sveltekit");
|
||||
adapterImport.removeDefaultImport();
|
||||
adapterImport.setDefaultImport("alchemy");
|
||||
} else {
|
||||
sourceFile.insertImportDeclaration(0, {
|
||||
moduleSpecifier: "alchemy/cloudflare/sveltekit",
|
||||
defaultImport: "alchemy",
|
||||
});
|
||||
}
|
||||
|
||||
const configVariable = sourceFile.getVariableDeclaration("config");
|
||||
if (configVariable) {
|
||||
const initializer = configVariable.getInitializer();
|
||||
if (Node.isObjectLiteralExpression(initializer)) {
|
||||
updateAdapterInConfig(initializer);
|
||||
}
|
||||
}
|
||||
|
||||
await project.save();
|
||||
} catch (error) {
|
||||
console.warn("Failed to update svelte.config.js:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function updateAdapterInConfig(configObject: Node): void {
|
||||
if (!Node.isObjectLiteralExpression(configObject)) return;
|
||||
|
||||
const kitProperty = configObject.getProperty("kit");
|
||||
if (kitProperty && Node.isPropertyAssignment(kitProperty)) {
|
||||
const kitInitializer = kitProperty.getInitializer();
|
||||
if (Node.isObjectLiteralExpression(kitInitializer)) {
|
||||
const adapterProperty = kitInitializer.getProperty("adapter");
|
||||
if (adapterProperty && Node.isPropertyAssignment(adapterProperty)) {
|
||||
const initializer = adapterProperty.getInitializer();
|
||||
if (Node.isCallExpression(initializer)) {
|
||||
const expression = initializer.getExpression();
|
||||
if (
|
||||
Node.isIdentifier(expression) &&
|
||||
expression.getText() === "adapter"
|
||||
) {
|
||||
expression.replaceWithText("alchemy");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import type { PackageManager } from "../../../types";
|
||||
import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
|
||||
export async function setupTanStackRouterAlchemyDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: ["alchemy", "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 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
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");
|
||||
}
|
||||
7
apps/cli/src/helpers/deployment/alchemy/index.ts
Normal file
7
apps/cli/src/helpers/deployment/alchemy/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export { setupNextAlchemyDeploy } from "./alchemy-next-setup";
|
||||
export { setupNuxtAlchemyDeploy } from "./alchemy-nuxt-setup";
|
||||
export { setupReactRouterAlchemyDeploy } from "./alchemy-react-router-setup";
|
||||
export { setupSolidAlchemyDeploy } from "./alchemy-solid-setup";
|
||||
export { setupSvelteAlchemyDeploy } from "./alchemy-svelte-setup";
|
||||
export { setupTanStackRouterAlchemyDeploy } from "./alchemy-tanstack-router-setup";
|
||||
export { setupTanStackStartAlchemyDeploy } from "./alchemy-tanstack-start-setup";
|
||||
111
apps/cli/src/helpers/deployment/server-deploy-setup.ts
Normal file
111
apps/cli/src/helpers/deployment/server-deploy-setup.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
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 { addPackageDependency } from "../../utils/add-package-deps";
|
||||
|
||||
export async function setupServerDeploy(config: ProjectConfig) {
|
||||
const { serverDeploy, webDeploy, projectDir } = config;
|
||||
const { packageManager } = config;
|
||||
|
||||
if (serverDeploy === "none") return;
|
||||
|
||||
if (serverDeploy === "alchemy" && webDeploy === "alchemy") {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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", "@cloudflare/workers-types"],
|
||||
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 = packageManager === "npm" ? "npm" : packageManager;
|
||||
await execa(runCmd, ["run", "cf-typegen"], { cwd: serverDir });
|
||||
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`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupAlchemyServerDeploy(
|
||||
serverDir: string,
|
||||
_packageManager: PackageManager,
|
||||
) {
|
||||
if (!(await fs.pathExists(serverDir))) return;
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: [
|
||||
"alchemy",
|
||||
"wrangler",
|
||||
"@types/node",
|
||||
"@cloudflare/workers-types",
|
||||
"dotenv",
|
||||
],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
|
||||
const packageJsonPath = path.join(serverDir, "package.json");
|
||||
if (await fs.pathExists(packageJsonPath)) {
|
||||
const packageJson = await fs.readJson(packageJsonPath);
|
||||
|
||||
packageJson.scripts = {
|
||||
...packageJson.scripts,
|
||||
dev: "wrangler dev --port=3000",
|
||||
build: "wrangler deploy --dry-run",
|
||||
deploy: "alchemy deploy",
|
||||
destroy: "alchemy destroy",
|
||||
"alchemy:dev": "alchemy dev",
|
||||
};
|
||||
|
||||
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||
}
|
||||
}
|
||||
94
apps/cli/src/helpers/deployment/web-deploy-setup.ts
Normal file
94
apps/cli/src/helpers/deployment/web-deploy-setup.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import type { PackageManager, ProjectConfig } from "../../types";
|
||||
import { setupCombinedAlchemyDeploy } from "./alchemy/alchemy-combined-setup";
|
||||
import { setupNextAlchemyDeploy } from "./alchemy/alchemy-next-setup";
|
||||
import { setupNuxtAlchemyDeploy } from "./alchemy/alchemy-nuxt-setup";
|
||||
import { setupReactRouterAlchemyDeploy } from "./alchemy/alchemy-react-router-setup";
|
||||
import { setupSolidAlchemyDeploy } from "./alchemy/alchemy-solid-setup";
|
||||
import { setupSvelteAlchemyDeploy } from "./alchemy/alchemy-svelte-setup";
|
||||
import { setupTanStackRouterAlchemyDeploy } from "./alchemy/alchemy-tanstack-router-setup";
|
||||
import { setupTanStackStartAlchemyDeploy } from "./alchemy/alchemy-tanstack-start-setup";
|
||||
import { setupNextWorkersDeploy } from "./workers/workers-next-setup";
|
||||
import { setupNuxtWorkersDeploy } from "./workers/workers-nuxt-setup";
|
||||
import { setupSvelteWorkersDeploy } from "./workers/workers-svelte-setup";
|
||||
import { setupTanstackStartWorkersDeploy } from "./workers/workers-tanstack-start-setup";
|
||||
import { setupWorkersVitePlugin } from "./workers/workers-vite-setup";
|
||||
|
||||
export async function setupWebDeploy(config: ProjectConfig) {
|
||||
const { webDeploy, serverDeploy, frontend, projectDir } = config;
|
||||
const { packageManager } = config;
|
||||
|
||||
if (webDeploy === "none") return;
|
||||
|
||||
if (webDeploy !== "wrangler" && webDeploy !== "alchemy") return;
|
||||
|
||||
if (webDeploy === "alchemy" && serverDeploy === "alchemy") {
|
||||
await setupCombinedAlchemyDeploy(projectDir, packageManager, config);
|
||||
return;
|
||||
}
|
||||
|
||||
const isNext = frontend.includes("next");
|
||||
const isNuxt = frontend.includes("nuxt");
|
||||
const isSvelte = frontend.includes("svelte");
|
||||
const isTanstackRouter = frontend.includes("tanstack-router");
|
||||
const isTanstackStart = frontend.includes("tanstack-start");
|
||||
const isReactRouter = frontend.includes("react-router");
|
||||
const isSolid = frontend.includes("solid");
|
||||
|
||||
if (webDeploy === "wrangler") {
|
||||
if (isNext) {
|
||||
await setupNextWorkersDeploy(projectDir, packageManager);
|
||||
} else if (isNuxt) {
|
||||
await setupNuxtWorkersDeploy(projectDir, packageManager);
|
||||
} else if (isSvelte) {
|
||||
await setupSvelteWorkersDeploy(projectDir, packageManager);
|
||||
} else if (isTanstackStart) {
|
||||
await setupTanstackStartWorkersDeploy(projectDir, packageManager);
|
||||
} else if (isTanstackRouter || isReactRouter || isSolid) {
|
||||
await setupWorkersWebDeploy(projectDir, packageManager);
|
||||
}
|
||||
} else if (webDeploy === "alchemy") {
|
||||
if (isNext) {
|
||||
await setupNextAlchemyDeploy(projectDir, packageManager);
|
||||
} else if (isNuxt) {
|
||||
await setupNuxtAlchemyDeploy(projectDir, packageManager);
|
||||
} else if (isSvelte) {
|
||||
await setupSvelteAlchemyDeploy(projectDir, packageManager);
|
||||
} else if (isTanstackStart) {
|
||||
await setupTanStackStartAlchemyDeploy(projectDir, packageManager);
|
||||
} else if (isTanstackRouter) {
|
||||
await setupTanStackRouterAlchemyDeploy(projectDir, packageManager);
|
||||
} else if (isReactRouter) {
|
||||
await setupReactRouterAlchemyDeploy(projectDir, packageManager);
|
||||
} else if (isSolid) {
|
||||
await setupSolidAlchemyDeploy(projectDir, packageManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function setupWorkersWebDeploy(
|
||||
projectDir: string,
|
||||
pkgManager: PackageManager,
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
|
||||
if (!(await fs.pathExists(webAppDir))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const packageJsonPath = path.join(webAppDir, "package.json");
|
||||
if (await fs.pathExists(packageJsonPath)) {
|
||||
const packageJson = await fs.readJson(packageJsonPath);
|
||||
|
||||
packageJson.scripts = {
|
||||
...packageJson.scripts,
|
||||
"wrangler:dev": "wrangler dev --port=3001",
|
||||
deploy: `${pkgManager} run build && wrangler deploy`,
|
||||
};
|
||||
|
||||
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||
}
|
||||
|
||||
await setupWorkersVitePlugin(projectDir);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import type { PackageManager } from "../../../types";
|
||||
import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
|
||||
export async function setupNextWorkersDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
await addPackageDependency({
|
||||
dependencies: ["@opennextjs/cloudflare"],
|
||||
devDependencies: ["wrangler"],
|
||||
projectDir: webAppDir,
|
||||
});
|
||||
|
||||
const packageJsonPath = path.join(webAppDir, "package.json");
|
||||
if (await fs.pathExists(packageJsonPath)) {
|
||||
const pkg = await fs.readJson(packageJsonPath);
|
||||
|
||||
pkg.scripts = {
|
||||
...pkg.scripts,
|
||||
preview: "opennextjs-cloudflare build && opennextjs-cloudflare preview",
|
||||
deploy: "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
|
||||
upload: "opennextjs-cloudflare build && opennextjs-cloudflare upload",
|
||||
"cf-typegen":
|
||||
"wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts",
|
||||
};
|
||||
|
||||
await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
|
||||
}
|
||||
}
|
||||
112
apps/cli/src/helpers/deployment/workers/workers-nuxt-setup.ts
Normal file
112
apps/cli/src/helpers/deployment/workers/workers-nuxt-setup.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import {
|
||||
type ArrayLiteralExpression,
|
||||
type CallExpression,
|
||||
Node,
|
||||
type ObjectLiteralExpression,
|
||||
type PropertyAssignment,
|
||||
SyntaxKind,
|
||||
} from "ts-morph";
|
||||
import type { PackageManager } from "../../../types";
|
||||
import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
import { tsProject } from "../../../utils/ts-morph";
|
||||
|
||||
export async function setupNuxtWorkersDeploy(
|
||||
projectDir: string,
|
||||
packageManager: PackageManager,
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: ["nitro-cloudflare-dev", "wrangler"],
|
||||
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: `${packageManager} run build && wrangler deploy`,
|
||||
"cf-typegen": "wrangler types",
|
||||
};
|
||||
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
||||
}
|
||||
|
||||
const nuxtConfigPath = path.join(webAppDir, "nuxt.config.ts");
|
||||
if (!(await fs.pathExists(nuxtConfigPath))) return;
|
||||
|
||||
const sourceFile = tsProject.addSourceFileAtPathIfExists(nuxtConfigPath);
|
||||
if (!sourceFile) return;
|
||||
|
||||
const defineCall = sourceFile
|
||||
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
||||
.find((expr) => {
|
||||
const expression = expr.getExpression();
|
||||
return (
|
||||
Node.isIdentifier(expression) &&
|
||||
expression.getText() === "defineNuxtConfig"
|
||||
);
|
||||
}) as CallExpression | undefined;
|
||||
|
||||
if (!defineCall) return;
|
||||
|
||||
const configObj = defineCall.getArguments()[0] as
|
||||
| ObjectLiteralExpression
|
||||
| undefined;
|
||||
if (!configObj) return;
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
|
||||
const compatProp = configObj.getProperty("compatibilityDate");
|
||||
if (compatProp && compatProp.getKind() === SyntaxKind.PropertyAssignment) {
|
||||
(compatProp as PropertyAssignment).setInitializer(`'${today}'`);
|
||||
} else {
|
||||
configObj.addPropertyAssignment({
|
||||
name: "compatibilityDate",
|
||||
initializer: `'${today}'`,
|
||||
});
|
||||
}
|
||||
|
||||
const nitroInitializer = `{
|
||||
preset: "cloudflare_module",
|
||||
cloudflare: {
|
||||
deployConfig: true,
|
||||
nodeCompat: true
|
||||
}
|
||||
}`;
|
||||
const nitroProp = configObj.getProperty("nitro");
|
||||
if (nitroProp && nitroProp.getKind() === SyntaxKind.PropertyAssignment) {
|
||||
(nitroProp as PropertyAssignment).setInitializer(nitroInitializer);
|
||||
} else {
|
||||
configObj.addPropertyAssignment({
|
||||
name: "nitro",
|
||||
initializer: nitroInitializer,
|
||||
});
|
||||
}
|
||||
|
||||
const modulesProp = configObj.getProperty("modules");
|
||||
if (modulesProp && modulesProp.getKind() === SyntaxKind.PropertyAssignment) {
|
||||
const arrayExpr = modulesProp.getFirstDescendantByKind(
|
||||
SyntaxKind.ArrayLiteralExpression,
|
||||
) as ArrayLiteralExpression | undefined;
|
||||
if (arrayExpr) {
|
||||
const alreadyHas = arrayExpr
|
||||
.getElements()
|
||||
.some(
|
||||
(el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev",
|
||||
);
|
||||
if (!alreadyHas) arrayExpr.addElement("'nitro-cloudflare-dev'");
|
||||
}
|
||||
} else {
|
||||
configObj.addPropertyAssignment({
|
||||
name: "modules",
|
||||
initializer: "['nitro-cloudflare-dev']",
|
||||
});
|
||||
}
|
||||
|
||||
await tsProject.save();
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import type { ImportDeclaration } from "ts-morph";
|
||||
import type { PackageManager } from "../../../types";
|
||||
import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
import { tsProject } from "../../../utils/ts-morph";
|
||||
|
||||
export async function setupSvelteWorkersDeploy(
|
||||
projectDir: string,
|
||||
packageManager: PackageManager,
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: ["@sveltejs/adapter-cloudflare", "wrangler"],
|
||||
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: `${packageManager} run build && wrangler deploy`,
|
||||
"cf-typegen": "wrangler types ./src/worker-configuration.d.ts",
|
||||
};
|
||||
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
||||
}
|
||||
|
||||
const possibleConfigFiles = [
|
||||
path.join(webAppDir, "svelte.config.js"),
|
||||
path.join(webAppDir, "svelte.config.ts"),
|
||||
];
|
||||
|
||||
const existingConfigPath = (
|
||||
await Promise.all(
|
||||
possibleConfigFiles.map(async (p) => ((await fs.pathExists(p)) ? p : "")),
|
||||
)
|
||||
).find((p) => p);
|
||||
|
||||
if (existingConfigPath) {
|
||||
const sourceFile =
|
||||
tsProject.addSourceFileAtPathIfExists(existingConfigPath);
|
||||
if (!sourceFile) return;
|
||||
|
||||
const adapterImport = sourceFile
|
||||
.getImportDeclarations()
|
||||
.find((imp: ImportDeclaration) =>
|
||||
["@sveltejs/adapter-auto", "@sveltejs/adapter-node"].includes(
|
||||
imp.getModuleSpecifierValue(),
|
||||
),
|
||||
);
|
||||
|
||||
if (adapterImport) {
|
||||
adapterImport.setModuleSpecifier("@sveltejs/adapter-cloudflare");
|
||||
} else {
|
||||
const alreadyHasCloudflare = sourceFile
|
||||
.getImportDeclarations()
|
||||
.some(
|
||||
(imp) =>
|
||||
imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare",
|
||||
);
|
||||
if (!alreadyHasCloudflare) {
|
||||
sourceFile.insertImportDeclaration(0, {
|
||||
defaultImport: "adapter",
|
||||
moduleSpecifier: "@sveltejs/adapter-cloudflare",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await tsProject.save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import {
|
||||
type CallExpression,
|
||||
Node,
|
||||
type ObjectLiteralExpression,
|
||||
SyntaxKind,
|
||||
} from "ts-morph";
|
||||
import type { PackageManager } from "../../../types";
|
||||
import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
import { ensureArrayProperty, tsProject } from "../../../utils/ts-morph";
|
||||
|
||||
export async function setupTanstackStartWorkersDeploy(
|
||||
projectDir: string,
|
||||
packageManager: PackageManager,
|
||||
) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: ["wrangler"],
|
||||
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: `${packageManager} run build && wrangler deploy`,
|
||||
"cf-typegen": "wrangler types --env-interface Env",
|
||||
};
|
||||
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
||||
}
|
||||
|
||||
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
||||
if (!(await fs.pathExists(viteConfigPath))) return;
|
||||
|
||||
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
||||
if (!sourceFile) return;
|
||||
|
||||
const defineCall = sourceFile
|
||||
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
||||
.find((expr) => {
|
||||
const expression = expr.getExpression();
|
||||
return (
|
||||
Node.isIdentifier(expression) && expression.getText() === "defineConfig"
|
||||
);
|
||||
}) as CallExpression | undefined;
|
||||
|
||||
if (!defineCall) return;
|
||||
|
||||
const configObj = defineCall.getArguments()[0] as
|
||||
| ObjectLiteralExpression
|
||||
| undefined;
|
||||
if (!configObj) return;
|
||||
|
||||
const pluginsArray = ensureArrayProperty(configObj, "plugins");
|
||||
|
||||
const tanstackPluginIndex = pluginsArray
|
||||
.getElements()
|
||||
.findIndex((el) => el.getText().includes("tanstackStart("));
|
||||
|
||||
const tanstackPluginText = 'tanstackStart({ target: "cloudflare-module" })';
|
||||
|
||||
if (tanstackPluginIndex === -1) {
|
||||
pluginsArray.addElement(tanstackPluginText);
|
||||
} else {
|
||||
pluginsArray
|
||||
.getElements()
|
||||
[tanstackPluginIndex].replaceWithText(tanstackPluginText);
|
||||
}
|
||||
|
||||
await tsProject.save();
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import {
|
||||
type CallExpression,
|
||||
Node,
|
||||
type ObjectLiteralExpression,
|
||||
SyntaxKind,
|
||||
} from "ts-morph";
|
||||
import { addPackageDependency } from "../../../utils/add-package-deps";
|
||||
import { ensureArrayProperty, tsProject } from "../../../utils/ts-morph";
|
||||
|
||||
export async function setupWorkersVitePlugin(projectDir: string) {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
||||
|
||||
if (!(await fs.pathExists(viteConfigPath))) {
|
||||
throw new Error("vite.config.ts not found in web app directory");
|
||||
}
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: ["@cloudflare/vite-plugin", "wrangler"],
|
||||
projectDir: webAppDir,
|
||||
});
|
||||
|
||||
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
||||
if (!sourceFile) {
|
||||
throw new Error("vite.config.ts not found in web app directory");
|
||||
}
|
||||
|
||||
const hasCloudflareImport = sourceFile
|
||||
.getImportDeclarations()
|
||||
.some((imp) => imp.getModuleSpecifierValue() === "@cloudflare/vite-plugin");
|
||||
|
||||
if (!hasCloudflareImport) {
|
||||
sourceFile.insertImportDeclaration(0, {
|
||||
namedImports: ["cloudflare"],
|
||||
moduleSpecifier: "@cloudflare/vite-plugin",
|
||||
});
|
||||
}
|
||||
|
||||
const defineCall = sourceFile
|
||||
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
||||
.find((expr) => {
|
||||
const expression = expr.getExpression();
|
||||
return (
|
||||
Node.isIdentifier(expression) && expression.getText() === "defineConfig"
|
||||
);
|
||||
});
|
||||
|
||||
if (!defineCall) {
|
||||
throw new Error("Could not find defineConfig call in vite config");
|
||||
}
|
||||
|
||||
const callExpr = defineCall as CallExpression;
|
||||
const configObject = callExpr.getArguments()[0] as
|
||||
| ObjectLiteralExpression
|
||||
| undefined;
|
||||
|
||||
if (!configObject) {
|
||||
throw new Error("defineConfig argument is not an object literal");
|
||||
}
|
||||
|
||||
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
||||
|
||||
const hasCloudflarePlugin = pluginsArray
|
||||
.getElements()
|
||||
.some((el) => el.getText().includes("cloudflare("));
|
||||
|
||||
if (!hasCloudflarePlugin) {
|
||||
pluginsArray.addElement("cloudflare()");
|
||||
}
|
||||
|
||||
await tsProject.save();
|
||||
}
|
||||
Reference in New Issue
Block a user