diff --git a/.changeset/heavy-carpets-jump.md b/.changeset/heavy-carpets-jump.md new file mode 100644 index 0000000..46ba809 --- /dev/null +++ b/.changeset/heavy-carpets-jump.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": minor +--- + +Add Progressive Web App (PWA) support diff --git a/apps/cli/src/constants.ts b/apps/cli/src/constants.ts index 27fdbac..1a712e4 100644 --- a/apps/cli/src/constants.ts +++ b/apps/cli/src/constants.ts @@ -28,6 +28,9 @@ export const dependencyVersionMap = { "@prisma/client": "^5.7.1", prisma: "^5.7.1", + + "vite-plugin-pwa": "^0.21.2", + "@vite-pwa/assets-generator": "^0.2.6", } 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 7d4a0da..20e26fc 100644 --- a/apps/cli/src/helpers/addons-setup.ts +++ b/apps/cli/src/helpers/addons-setup.ts @@ -1,11 +1,16 @@ import path from "node:path"; import fs from "fs-extra"; +import { PKG_ROOT } from "../constants"; import type { ProjectAddons } from "../types"; +import { addPackageDependency } from "../utils/add-package-deps"; export async function setupAddons(projectDir: string, addons: ProjectAddons[]) { if (addons.includes("docker")) { await setupDocker(projectDir); } + if (addons.includes("pwa")) { + await setupPwa(projectDir); + } } async function setupDocker(projectDir: string) { @@ -89,3 +94,30 @@ node_modules dockerignoreContent, ); } + +async function setupPwa(projectDir: string) { + const pwaTemplateDir = path.join(PKG_ROOT, "template/with-pwa"); + if (await fs.pathExists(pwaTemplateDir)) { + await fs.copy(pwaTemplateDir, projectDir, { overwrite: true }); + } + + const clientPackageDir = path.join(projectDir, "packages/client"); + + addPackageDependency({ + dependencies: ["vite-plugin-pwa"], + devDependencies: ["@vite-pwa/assets-generator"], + projectDir: clientPackageDir, + }); + + const clientPackageJsonPath = path.join(clientPackageDir, "package.json"); + if (await fs.pathExists(clientPackageJsonPath)) { + const packageJson = await fs.readJson(clientPackageJsonPath); + + packageJson.scripts = { + ...packageJson.scripts, + "generate-pwa-assets": "pwa-assets-generator", + }; + + await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 }); + } +} diff --git a/apps/cli/src/helpers/create-readme.ts b/apps/cli/src/helpers/create-readme.ts index 8bd7258..06ea714 100644 --- a/apps/cli/src/helpers/create-readme.ts +++ b/apps/cli/src/helpers/create-readme.ts @@ -135,15 +135,6 @@ cd packages/server && ${packageManagerRunCmd} db:local `; } - if (auth) { - setup += ` -3. Generate the authentication schema: -\`\`\`bash -cd packages/server && ${packageManagerRunCmd} auth:generate -\`\`\` -`; - } - setup += ` ${auth ? "4" : "3"}. ${ orm === "prisma" diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index b956df8..53019ed 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -31,6 +31,7 @@ async function main() { .option("--auth", "Include authentication") .option("--no-auth", "Exclude authentication") .option("--docker", "Include Docker setup") + .option("--pwa", "Include Progressive Web App support") .option("--no-addons", "Skip all additional addons") .option("--git", "Include git setup") .option("--no-git", "Skip git initialization") @@ -64,17 +65,20 @@ async function main() { ...(options.prisma && { orm: "prisma" }), ...("auth" in options && { auth: options.auth }), ...(options.npm && { packageManager: "npm" }), - ...(options.pnpm && { packageManager: "pnpm" }), + ...(options.pnpm && { packageManager: " pnpm" }), ...(options.yarn && { packageManager: "yarn" }), ...(options.bun && { packageManager: "bun" }), ...("git" in options && { git: options.git }), ...("install" in options && { noInstall: !options.install }), ...("turso" in options && { turso: options.turso }), - ...((options.docker || options.addons === false) && { + ...((options.docker || options.pwa || options.addons === false) && { addons: options.addons === false ? [] - : ([...(options.docker ? ["docker"] : [])] as ProjectAddons[]), + : ([ + ...(options.docker ? ["docker"] : []), + ...(options.pwa ? ["pwa"] : []), + ] as ProjectAddons[]), }), }; diff --git a/apps/cli/src/prompts/addons.ts b/apps/cli/src/prompts/addons.ts index 5b229f2..d2c666c 100644 --- a/apps/cli/src/prompts/addons.ts +++ b/apps/cli/src/prompts/addons.ts @@ -15,6 +15,11 @@ export async function getAddonsChoice( label: "Docker setup", hint: "Containerize your application", }, + { + value: "pwa", + label: "PWA (Progressive Web App)", + hint: "Make your app installable and work offline", + }, ], required: false, }); diff --git a/apps/cli/src/types.ts b/apps/cli/src/types.ts index 680f3ba..033fa15 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"; +export type ProjectAddons = "docker" | "pwa"; export interface ProjectConfig { projectName: string; diff --git a/apps/cli/template/base/packages/client/_gitignore b/apps/cli/template/base/packages/client/_gitignore index c9b51df..b61b002 100644 --- a/apps/cli/template/base/packages/client/_gitignore +++ b/apps/cli/template/base/packages/client/_gitignore @@ -19,3 +19,5 @@ dist/ *.env* !.env.example + +dev-dist diff --git a/apps/cli/template/with-pwa/packages/client/public/logo.png b/apps/cli/template/with-pwa/packages/client/public/logo.png new file mode 100644 index 0000000..ac20b90 Binary files /dev/null and b/apps/cli/template/with-pwa/packages/client/public/logo.png differ diff --git a/apps/cli/template/with-pwa/packages/client/pwa-assets.config.ts b/apps/cli/template/with-pwa/packages/client/pwa-assets.config.ts new file mode 100644 index 0000000..60f5657 --- /dev/null +++ b/apps/cli/template/with-pwa/packages/client/pwa-assets.config.ts @@ -0,0 +1,12 @@ +import { + defineConfig, + minimal2023Preset as preset, +} from "@vite-pwa/assets-generator/config"; + +export default defineConfig({ + headLinkOptions: { + preset: "2023", + }, + preset, + images: ["public/logo.png"], +}); diff --git a/apps/cli/template/with-pwa/packages/client/vite.config.ts b/apps/cli/template/with-pwa/packages/client/vite.config.ts new file mode 100644 index 0000000..8a3af77 --- /dev/null +++ b/apps/cli/template/with-pwa/packages/client/vite.config.ts @@ -0,0 +1,35 @@ +import tailwindcss from "@tailwindcss/vite"; +import { TanStackRouterVite } from "@tanstack/router-plugin/vite"; +import react from "@vitejs/plugin-react"; +import path from "path"; +import { defineConfig } from "vite"; +import { VitePWA } from "vite-plugin-pwa"; + +export default defineConfig({ + plugins: [ + tailwindcss(), + TanStackRouterVite({}), + react(), + VitePWA({ + registerType: "autoUpdate", + manifest: { + name: "My App", + short_name: "My App ", + description: "My App", + theme_color: "#0c0c0c", + }, + pwaAssets: { + disabled: false, + config: true, + }, + devOptions: { + enabled: true, + }, + }), + ], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, +});