mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add cloudflare workers support for all frontends (#366)
This commit is contained in:
@@ -18,7 +18,7 @@ function exitWithError(message: string): never {
|
||||
}
|
||||
|
||||
export async function addAddonsToProject(
|
||||
input: AddInput & { addons: Addons[] },
|
||||
input: AddInput & { addons: Addons[]; suppressInstallMessage?: boolean },
|
||||
): Promise<void> {
|
||||
try {
|
||||
const projectDir = input.projectDir || process.cwd();
|
||||
@@ -55,6 +55,7 @@ export async function addAddonsToProject(
|
||||
install: input.install || false,
|
||||
dbSetup: detectedConfig.dbSetup || "none",
|
||||
api: detectedConfig.api || "none",
|
||||
webDeploy: detectedConfig.webDeploy || "none",
|
||||
};
|
||||
|
||||
for (const addon of input.addons) {
|
||||
@@ -88,7 +89,7 @@ export async function addAddonsToProject(
|
||||
projectDir,
|
||||
packageManager: config.packageManager,
|
||||
});
|
||||
} else {
|
||||
} else if (!input.suppressInstallMessage) {
|
||||
log.info(
|
||||
pc.yellow(
|
||||
`Run ${pc.bold(
|
||||
|
||||
113
apps/cli/src/helpers/project-generation/add-deployment.ts
Normal file
113
apps/cli/src/helpers/project-generation/add-deployment.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import path from "node:path";
|
||||
import { cancel, log } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import type { AddInput, ProjectConfig, WebDeploy } from "../../types";
|
||||
import { updateBtsConfig } from "../../utils/bts-config";
|
||||
import { setupWebDeploy } from "../setup/web-deploy-setup";
|
||||
import {
|
||||
detectProjectConfig,
|
||||
isBetterTStackProject,
|
||||
} from "./detect-project-config";
|
||||
import { installDependencies } from "./install-dependencies";
|
||||
import { setupDeploymentTemplates } from "./template-manager";
|
||||
|
||||
function exitWithError(message: string): never {
|
||||
cancel(pc.red(message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
export async function addDeploymentToProject(
|
||||
input: AddInput & { webDeploy: WebDeploy; suppressInstallMessage?: boolean },
|
||||
): Promise<void> {
|
||||
try {
|
||||
const projectDir = input.projectDir || process.cwd();
|
||||
|
||||
const isBetterTStack = await isBetterTStackProject(projectDir);
|
||||
if (!isBetterTStack) {
|
||||
exitWithError(
|
||||
"This doesn't appear to be a Better-T Stack project. Please run this command from the root of a Better-T Stack project.",
|
||||
);
|
||||
}
|
||||
|
||||
const detectedConfig = await detectProjectConfig(projectDir);
|
||||
if (!detectedConfig) {
|
||||
exitWithError(
|
||||
"Could not detect the project configuration. Please ensure this is a valid Better-T Stack project.",
|
||||
);
|
||||
}
|
||||
|
||||
if (detectedConfig.webDeploy === input.webDeploy) {
|
||||
exitWithError(
|
||||
`${input.webDeploy} deployment is already configured for this project.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (input.webDeploy === "workers") {
|
||||
const compatibleFrontends = [
|
||||
"tanstack-router",
|
||||
"react-router",
|
||||
"solid",
|
||||
"next",
|
||||
"svelte",
|
||||
];
|
||||
const hasCompatible = detectedConfig.frontend?.some((f) =>
|
||||
compatibleFrontends.includes(f),
|
||||
);
|
||||
if (!hasCompatible) {
|
||||
exitWithError(
|
||||
"Cloudflare Workers deployment requires a compatible web frontend (tanstack-router, react-router, solid, next, or svelte).",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const config: ProjectConfig = {
|
||||
projectName: detectedConfig.projectName || path.basename(projectDir),
|
||||
projectDir,
|
||||
relativePath: ".",
|
||||
database: detectedConfig.database || "none",
|
||||
orm: detectedConfig.orm || "none",
|
||||
backend: detectedConfig.backend || "none",
|
||||
runtime: detectedConfig.runtime || "none",
|
||||
frontend: detectedConfig.frontend || [],
|
||||
addons: detectedConfig.addons || [],
|
||||
examples: detectedConfig.examples || [],
|
||||
auth: detectedConfig.auth || false,
|
||||
git: false,
|
||||
packageManager:
|
||||
input.packageManager || detectedConfig.packageManager || "npm",
|
||||
install: input.install || false,
|
||||
dbSetup: detectedConfig.dbSetup || "none",
|
||||
api: detectedConfig.api || "none",
|
||||
webDeploy: input.webDeploy,
|
||||
};
|
||||
|
||||
log.info(
|
||||
pc.green(
|
||||
`Adding ${input.webDeploy} deployment to ${config.frontend.join("/")}`,
|
||||
),
|
||||
);
|
||||
|
||||
await setupDeploymentTemplates(projectDir, config);
|
||||
await setupWebDeploy(config);
|
||||
|
||||
await updateBtsConfig(projectDir, { webDeploy: input.webDeploy });
|
||||
|
||||
if (config.install) {
|
||||
await installDependencies({
|
||||
projectDir,
|
||||
packageManager: config.packageManager,
|
||||
});
|
||||
} else if (!input.suppressInstallMessage) {
|
||||
log.info(
|
||||
pc.yellow(
|
||||
`Run ${pc.bold(
|
||||
`${config.packageManager} install`,
|
||||
)} to install dependencies`,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
exitWithError(`Error adding deployment: ${message}`);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { DEFAULT_CONFIG } from "../../constants";
|
||||
import { getAddonsToAdd } from "../../prompts/addons";
|
||||
import { gatherConfig } from "../../prompts/config-prompts";
|
||||
import { getProjectName } from "../../prompts/project-name";
|
||||
import { getDeploymentToAdd } from "../../prompts/web-deploy";
|
||||
import type { AddInput, CreateInput, ProjectConfig } from "../../types";
|
||||
import { trackProjectCreation } from "../../utils/analytics";
|
||||
import { displayConfig } from "../../utils/display-config";
|
||||
@@ -17,8 +18,10 @@ import {
|
||||
import { renderTitle } from "../../utils/render-title";
|
||||
import { getProvidedFlags, processAndValidateFlags } from "../../validation";
|
||||
import { addAddonsToProject } from "./add-addons";
|
||||
import { addDeploymentToProject } from "./add-deployment";
|
||||
import { createProject } from "./create-project";
|
||||
import { detectProjectConfig } from "./detect-project-config";
|
||||
import { installDependencies } from "./install-dependencies";
|
||||
|
||||
export async function createProjectHandler(
|
||||
input: CreateInput & { projectName?: string },
|
||||
@@ -135,45 +138,84 @@ export async function createProjectHandler(
|
||||
|
||||
export async function addAddonsHandler(input: AddInput): Promise<void> {
|
||||
try {
|
||||
const projectDir = input.projectDir || process.cwd();
|
||||
const detectedConfig = await detectProjectConfig(projectDir);
|
||||
|
||||
if (!detectedConfig) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Could not detect project configuration. Please ensure this is a valid Better-T Stack project.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!input.addons || input.addons.length === 0) {
|
||||
const projectDir = input.projectDir || process.cwd();
|
||||
const detectedConfig = await detectProjectConfig(projectDir);
|
||||
|
||||
if (!detectedConfig) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Could not detect project configuration. Please ensure this is a valid Better-T Stack project.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const addonsPrompt = await getAddonsToAdd(
|
||||
detectedConfig.frontend || [],
|
||||
detectedConfig.addons || [],
|
||||
);
|
||||
|
||||
if (addonsPrompt.length === 0) {
|
||||
outro(
|
||||
pc.yellow(
|
||||
"No addons to add or all compatible addons are already present.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
if (addonsPrompt.length > 0) {
|
||||
input.addons = addonsPrompt;
|
||||
}
|
||||
|
||||
input.addons = addonsPrompt;
|
||||
}
|
||||
|
||||
if (!input.addons || input.addons.length === 0) {
|
||||
outro(pc.yellow("No addons specified to add."));
|
||||
if (!input.webDeploy) {
|
||||
const deploymentPrompt = await getDeploymentToAdd(
|
||||
detectedConfig.frontend || [],
|
||||
detectedConfig.webDeploy,
|
||||
);
|
||||
|
||||
if (deploymentPrompt !== "none") {
|
||||
input.webDeploy = deploymentPrompt;
|
||||
}
|
||||
}
|
||||
|
||||
const packageManager =
|
||||
input.packageManager || detectedConfig.packageManager || "npm";
|
||||
|
||||
let somethingAdded = false;
|
||||
|
||||
if (input.addons && input.addons.length > 0) {
|
||||
await addAddonsToProject({
|
||||
...input,
|
||||
install: false,
|
||||
suppressInstallMessage: true,
|
||||
addons: input.addons,
|
||||
});
|
||||
somethingAdded = true;
|
||||
}
|
||||
|
||||
if (input.webDeploy && input.webDeploy !== "none") {
|
||||
await addDeploymentToProject({
|
||||
...input,
|
||||
install: false,
|
||||
suppressInstallMessage: true,
|
||||
webDeploy: input.webDeploy,
|
||||
});
|
||||
somethingAdded = true;
|
||||
}
|
||||
|
||||
if (!somethingAdded) {
|
||||
outro(pc.yellow("No addons or deployment configurations to add."));
|
||||
return;
|
||||
}
|
||||
|
||||
await addAddonsToProject({
|
||||
...input,
|
||||
addons: input.addons,
|
||||
});
|
||||
if (input.install) {
|
||||
await installDependencies({
|
||||
projectDir,
|
||||
packageManager,
|
||||
});
|
||||
} else {
|
||||
log.info(
|
||||
pc.yellow(
|
||||
`Run ${pc.bold(`${packageManager} install`)} to install dependencies`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
outro(pc.green("Add command completed successfully!"));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
generateCloudflareWorkerTypes,
|
||||
setupRuntime,
|
||||
} from "../setup/runtime-setup";
|
||||
import { setupWebDeploy } from "../setup/web-deploy-setup";
|
||||
import { createReadme } from "./create-readme";
|
||||
import { setupEnvironmentVariables } from "./env-setup";
|
||||
import { initializeGit } from "./git";
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
setupAuthTemplate,
|
||||
setupBackendFramework,
|
||||
setupDbOrmTemplates,
|
||||
setupDeploymentTemplates,
|
||||
setupExamplesTemplate,
|
||||
setupFrontendTemplates,
|
||||
} from "./template-manager";
|
||||
@@ -49,6 +51,8 @@ export async function createProject(options: ProjectConfig) {
|
||||
}
|
||||
await setupAddonsTemplate(projectDir, options);
|
||||
|
||||
await setupDeploymentTemplates(projectDir, options);
|
||||
|
||||
await setupApi(options);
|
||||
|
||||
if (!isConvex) {
|
||||
@@ -70,6 +74,8 @@ export async function createProject(options: ProjectConfig) {
|
||||
|
||||
await handleExtras(projectDir, options);
|
||||
|
||||
await setupWebDeploy(options);
|
||||
|
||||
await setupEnvironmentVariables(options);
|
||||
await updatePackageConfigurations(projectDir, options);
|
||||
await createReadme(projectDir, options);
|
||||
|
||||
@@ -23,6 +23,7 @@ export async function detectProjectConfig(
|
||||
packageManager: btsConfig.packageManager,
|
||||
dbSetup: btsConfig.dbSetup,
|
||||
api: btsConfig.api,
|
||||
webDeploy: btsConfig.webDeploy,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -30,5 +30,5 @@ export async function initializeGit(
|
||||
}
|
||||
|
||||
await $({ cwd: projectDir })`git add -A`;
|
||||
await $({ cwd: projectDir })`git commit -m ${"Initial commit"}`;
|
||||
await $({ cwd: projectDir })`git commit -m ${"initial commit"}`;
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ function getTauriInstructions(runCmd?: string): string {
|
||||
function getPwaInstructions(): string {
|
||||
return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow(
|
||||
"NOTE:",
|
||||
)} There is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
|
||||
)} There is a known compatibility issue between VitePWA \nand React Router v7.See: https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
|
||||
}
|
||||
|
||||
function getStarlightInstructions(runCmd?: string): string {
|
||||
|
||||
@@ -832,3 +832,47 @@ export async function handleExtras(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupDeploymentTemplates(
|
||||
projectDir: string,
|
||||
context: ProjectConfig,
|
||||
): Promise<void> {
|
||||
if (context.webDeploy === "none") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.webDeploy === "workers") {
|
||||
const webAppDir = path.join(projectDir, "apps/web");
|
||||
if (!(await fs.pathExists(webAppDir))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const frontends = context.frontend;
|
||||
|
||||
const templateMap: Record<string, string> = {
|
||||
"tanstack-router": "react/tanstack-router",
|
||||
"react-router": "react/react-router",
|
||||
solid: "solid",
|
||||
next: "react/next",
|
||||
nuxt: "nuxt",
|
||||
svelte: "svelte",
|
||||
};
|
||||
|
||||
for (const f of frontends) {
|
||||
if (templateMap[f]) {
|
||||
const deployTemplateSrc = path.join(
|
||||
PKG_ROOT,
|
||||
`templates/deploy/web/${templateMap[f]}`,
|
||||
);
|
||||
if (await fs.pathExists(deployTemplateSrc)) {
|
||||
await processAndCopyFiles(
|
||||
"**/*",
|
||||
deployTemplateSrc,
|
||||
webAppDir,
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { Frontend, ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
import { setupStarlight } from "./starlight-setup";
|
||||
import { setupTauri } from "./tauri-setup";
|
||||
import { addPwaToViteConfig } from "./vite-pwa-setup";
|
||||
|
||||
export async function setupAddons(config: ProjectConfig, isAddCommand = false) {
|
||||
const { addons, frontend, projectDir } = config;
|
||||
@@ -149,4 +150,10 @@ async function setupPwa(projectDir: string, frontends: Frontend[]) {
|
||||
|
||||
await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
|
||||
}
|
||||
|
||||
const viteConfigTs = path.join(clientPackageDir, "vite.config.ts");
|
||||
|
||||
if (await fs.pathExists(viteConfigTs)) {
|
||||
await addPwaToViteConfig(viteConfigTs, path.basename(projectDir));
|
||||
}
|
||||
}
|
||||
|
||||
73
apps/cli/src/helpers/setup/vite-pwa-setup.ts
Normal file
73
apps/cli/src/helpers/setup/vite-pwa-setup.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
type CallExpression,
|
||||
Node,
|
||||
type ObjectLiteralExpression,
|
||||
SyntaxKind,
|
||||
} from "ts-morph";
|
||||
import { ensureArrayProperty, tsProject } from "../../utils/ts-morph";
|
||||
|
||||
export async function addPwaToViteConfig(
|
||||
viteConfigPath: string,
|
||||
projectName: string,
|
||||
): Promise<void> {
|
||||
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
||||
if (!sourceFile) {
|
||||
throw new Error("vite config not found");
|
||||
}
|
||||
|
||||
const hasImport = sourceFile
|
||||
.getImportDeclarations()
|
||||
.some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa");
|
||||
|
||||
if (!hasImport) {
|
||||
sourceFile.insertImportDeclaration(0, {
|
||||
namedImports: ["VitePWA"],
|
||||
moduleSpecifier: "vite-plugin-pwa",
|
||||
});
|
||||
}
|
||||
|
||||
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 alreadyPresent = pluginsArray
|
||||
.getElements()
|
||||
.some((el) => el.getText().startsWith("VitePWA("));
|
||||
|
||||
if (!alreadyPresent) {
|
||||
pluginsArray.addElement(
|
||||
`VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
manifest: {
|
||||
name: "${projectName}",
|
||||
short_name: "${projectName}",
|
||||
description: "${projectName} - PWA Application",
|
||||
theme_color: "#0c0c0c",
|
||||
},
|
||||
pwaAssets: { disabled: false, config: true },
|
||||
devOptions: { enabled: true },
|
||||
})`,
|
||||
);
|
||||
}
|
||||
|
||||
await tsProject.save();
|
||||
}
|
||||
89
apps/cli/src/helpers/setup/web-deploy-setup.ts
Normal file
89
apps/cli/src/helpers/setup/web-deploy-setup.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import type { PackageManager, ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
import { setupNuxtWorkersDeploy } from "./workers-nuxt-setup";
|
||||
import { setupSvelteWorkersDeploy } from "./workers-svelte-setup";
|
||||
import { setupWorkersVitePlugin } from "./workers-vite-setup";
|
||||
|
||||
export async function setupWebDeploy(config: ProjectConfig): Promise<void> {
|
||||
const { webDeploy, frontend, projectDir } = config;
|
||||
const { packageManager } = config;
|
||||
|
||||
if (webDeploy === "none") return;
|
||||
|
||||
if (webDeploy !== "workers") return;
|
||||
|
||||
const isNext = frontend.includes("next");
|
||||
const isNuxt = frontend.includes("nuxt");
|
||||
const isSvelte = frontend.includes("svelte");
|
||||
const isTanstackRouter = frontend.includes("tanstack-router");
|
||||
const isReactRouter = frontend.includes("react-router");
|
||||
const isSolid = frontend.includes("solid");
|
||||
|
||||
if (isNext) {
|
||||
await setupNextWorkersDeploy(projectDir, packageManager);
|
||||
} else if (isNuxt) {
|
||||
await setupNuxtWorkersDeploy(projectDir, packageManager);
|
||||
} else if (isSvelte) {
|
||||
await setupSvelteWorkersDeploy(projectDir, packageManager);
|
||||
} else if (isTanstackRouter || isReactRouter || isSolid) {
|
||||
await setupWorkersWebDeploy(projectDir, packageManager);
|
||||
}
|
||||
}
|
||||
|
||||
async function setupWorkersWebDeploy(
|
||||
projectDir: string,
|
||||
pkgManager: PackageManager,
|
||||
): Promise<void> {
|
||||
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);
|
||||
}
|
||||
|
||||
async function setupNextWorkersDeploy(
|
||||
projectDir: string,
|
||||
_packageManager: PackageManager,
|
||||
): Promise<void> {
|
||||
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/setup/workers-nuxt-setup.ts
Normal file
112
apps/cli/src/helpers/setup/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,
|
||||
): Promise<void> {
|
||||
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();
|
||||
}
|
||||
74
apps/cli/src/helpers/setup/workers-svelte-setup.ts
Normal file
74
apps/cli/src/helpers/setup/workers-svelte-setup.ts
Normal file
@@ -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,
|
||||
): Promise<void> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
76
apps/cli/src/helpers/setup/workers-vite-setup.ts
Normal file
76
apps/cli/src/helpers/setup/workers-vite-setup.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
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,
|
||||
): Promise<void> {
|
||||
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