diff --git a/.changeset/wise-crews-like.md b/.changeset/wise-crews-like.md new file mode 100644 index 0000000..05db899 --- /dev/null +++ b/.changeset/wise-crews-like.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": minor +--- + +Add Tauri Desktop App addon diff --git a/apps/cli/src/constants.ts b/apps/cli/src/constants.ts index 1a712e4..39cf6eb 100644 --- a/apps/cli/src/constants.ts +++ b/apps/cli/src/constants.ts @@ -31,6 +31,8 @@ export const dependencyVersionMap = { "vite-plugin-pwa": "^0.21.2", "@vite-pwa/assets-generator": "^0.2.6", + + "@tauri-apps/cli": "^2.4.0", } as const; export type AvailableDependencies = keyof typeof dependencyVersionMap; diff --git a/apps/cli/src/helpers/addons-setup.ts b/apps/cli/src/helpers/addons-setup.ts index 20e26fc..7c3e33a 100644 --- a/apps/cli/src/helpers/addons-setup.ts +++ b/apps/cli/src/helpers/addons-setup.ts @@ -1,16 +1,24 @@ import path from "node:path"; import fs from "fs-extra"; import { PKG_ROOT } from "../constants"; -import type { ProjectAddons } from "../types"; +import type { PackageManager, ProjectAddons } from "../types"; import { addPackageDependency } from "../utils/add-package-deps"; +import { setupTauri } from "./tauri-setup"; -export async function setupAddons(projectDir: string, addons: ProjectAddons[]) { +export async function setupAddons( + projectDir: string, + addons: ProjectAddons[], + packageManager: PackageManager, +) { if (addons.includes("docker")) { await setupDocker(projectDir); } if (addons.includes("pwa")) { await setupPwa(projectDir); } + if (addons.includes("tauri")) { + await setupTauri(projectDir, packageManager); + } } async function setupDocker(projectDir: string) { diff --git a/apps/cli/src/helpers/create-project.ts b/apps/cli/src/helpers/create-project.ts index c46fef3..6996b20 100644 --- a/apps/cli/src/helpers/create-project.ts +++ b/apps/cli/src/helpers/create-project.ts @@ -126,7 +126,7 @@ export async function createProject(options: ProjectConfig): Promise { } if (options.addons.length > 0) { - await setupAddons(projectDir, options.addons); + await setupAddons(projectDir, options.addons, options.packageManager); } await updatePackageConfigurations(projectDir, options); @@ -138,6 +138,7 @@ export async function createProject(options: ProjectConfig): Promise { options.packageManager, !options.noInstall, options.orm, + options.addons, ); return projectDir; diff --git a/apps/cli/src/helpers/post-installation.ts b/apps/cli/src/helpers/post-installation.ts index 7e9df29..b17ad98 100644 --- a/apps/cli/src/helpers/post-installation.ts +++ b/apps/cli/src/helpers/post-installation.ts @@ -1,6 +1,11 @@ import { log } from "@clack/prompts"; import pc from "picocolors"; -import type { PackageManager, ProjectDatabase, ProjectOrm } from "../types"; +import type { + PackageManager, + ProjectAddons, + ProjectDatabase, + ProjectOrm, +} from "../types"; export function displayPostInstallInstructions( database: ProjectDatabase, @@ -8,6 +13,7 @@ export function displayPostInstallInstructions( packageManager: PackageManager, depsInstalled: boolean, orm?: ProjectOrm, + addons?: ProjectAddons[], ) { const runCmd = packageManager === "npm" ? "npm run" : packageManager; const cdCmd = `cd ${projectName}`; @@ -22,7 +28,8 @@ ${pc.bold("Your project will be available at:")} ${pc.cyan("•")} Frontend: http://localhost:3001 ${pc.cyan("•")} API: http://localhost:3000 -${database !== "none" ? getDatabaseInstructions(database, orm, runCmd) : ""}`); +${database !== "none" ? getDatabaseInstructions(database, orm, runCmd) : ""} +${addons?.includes("tauri") ? getTauriInstructions(runCmd) : ""}`); } function getDatabaseInstructions( @@ -63,3 +70,7 @@ function getDatabaseInstructions( ? `${pc.bold("Database commands:")}\n${instructions.join("\n")}\n\n` : ""; } + +function getTauriInstructions(runCmd?: string): string { + return `${pc.bold("Desktop app with Tauri:")}\n${pc.cyan("•")} Start desktop app: ${pc.dim(`cd packages/client && ${runCmd} desktop:dev`)}\n${pc.cyan("•")} Build desktop app: ${pc.dim(`cd packages/client && ${runCmd} desktop:build`)}\n${pc.yellow("NOTE:")} Tauri requires Rust and platform-specific dependencies. See: ${pc.dim("https://v2.tauri.app/start/prerequisites/")}\n\n`; +} diff --git a/apps/cli/src/helpers/tauri-setup.ts b/apps/cli/src/helpers/tauri-setup.ts new file mode 100644 index 0000000..10c188f --- /dev/null +++ b/apps/cli/src/helpers/tauri-setup.ts @@ -0,0 +1,69 @@ +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 } from "../types"; +import { addPackageDependency } from "../utils/add-package-deps"; + +export async function setupTauri( + projectDir: string, + packageManager: PackageManager, +): Promise { + const s = spinner(); + const clientPackageDir = path.join(projectDir, "packages/client"); + + try { + s.start("Setting up Tauri desktop app support..."); + + addPackageDependency({ + devDependencies: ["@tauri-apps/cli"], + projectDir: clientPackageDir, + }); + + const clientPackageJsonPath = path.join(clientPackageDir, "package.json"); + if (await fs.pathExists(clientPackageJsonPath)) { + const packageJson = await fs.readJson(clientPackageJsonPath); + + // Add Tauri scripts + packageJson.scripts = { + ...packageJson.scripts, + tauri: "tauri", + "desktop:dev": "tauri dev", + "desktop:build": "tauri build", + }; + + await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 }); + } + + // Initialize Tauri in the client directory + // This creates the src-tauri folder structure with necessary config files + + await execa( + "npx", + [ + "@tauri-apps/cli@latest", + "init", + `--app-name=${path.basename(projectDir)}`, + `--window-title=${path.basename(projectDir)}`, + "--frontend-dist=dist", + "--dev-url=http://localhost:3001", + `--before-dev-command=${packageManager} run dev`, + `--before-build-command=${packageManager} run build`, + ], + { + cwd: clientPackageDir, + env: { + CI: "true", + }, + }, + ); + s.stop("Tauri desktop app support configured successfully!"); + } catch (error) { + s.stop(pc.red("Failed to set up Tauri")); + if (error instanceof Error) { + log.error(pc.red(error.message)); + } + throw error; + } +} diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index 53019ed..c5d5f47 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -32,6 +32,7 @@ async function main() { .option("--no-auth", "Exclude authentication") .option("--docker", "Include Docker setup") .option("--pwa", "Include Progressive Web App support") + .option("--tauri", "Include Tauri desktop app support") .option("--no-addons", "Skip all additional addons") .option("--git", "Include git setup") .option("--no-git", "Skip git initialization") @@ -71,13 +72,17 @@ async function main() { ...("git" in options && { git: options.git }), ...("install" in options && { noInstall: !options.install }), ...("turso" in options && { turso: options.turso }), - ...((options.docker || options.pwa || options.addons === false) && { + ...((options.docker || + options.pwa || + options.tauri || + options.addons === false) && { addons: options.addons === false ? [] : ([ ...(options.docker ? ["docker"] : []), ...(options.pwa ? ["pwa"] : []), + ...(options.tauri ? ["tauri"] : []), ] as ProjectAddons[]), }), }; diff --git a/apps/cli/src/prompts/addons.ts b/apps/cli/src/prompts/addons.ts index d2c666c..5aba9ed 100644 --- a/apps/cli/src/prompts/addons.ts +++ b/apps/cli/src/prompts/addons.ts @@ -20,6 +20,11 @@ export async function getAddonsChoice( label: "PWA (Progressive Web App)", hint: "Make your app installable and work offline", }, + { + value: "tauri", + label: "Tauri Desktop App", + hint: "Build native desktop apps from your web frontend", + }, ], required: false, }); diff --git a/apps/cli/src/types.ts b/apps/cli/src/types.ts index 033fa15..b894453 100644 --- a/apps/cli/src/types.ts +++ b/apps/cli/src/types.ts @@ -1,7 +1,7 @@ export type ProjectDatabase = "sqlite" | "postgres" | "none"; export type ProjectOrm = "drizzle" | "prisma" | "none"; export type PackageManager = "npm" | "pnpm" | "yarn" | "bun"; -export type ProjectAddons = "docker" | "pwa"; +export type ProjectAddons = "docker" | "pwa" | "tauri"; export interface ProjectConfig { projectName: string;