From 66a47c7c614a16cb09fe634db269ce1029a401a1 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Sat, 22 Mar 2025 13:05:45 +0530 Subject: [PATCH] Add Progressive Web App (PWA) support Add PWA setup with vite-plugin-pwa, assets generator, and templates Include CLI flag and interactive prompt option for PWA installation --- .changeset/heavy-carpets-jump.md | 5 +++ apps/cli/src/constants.ts | 3 ++ apps/cli/src/helpers/addons-setup.ts | 32 ++++++++++++++++ apps/cli/src/helpers/create-readme.ts | 9 ----- apps/cli/src/index.ts | 10 +++-- apps/cli/src/prompts/addons.ts | 5 +++ apps/cli/src/types.ts | 2 +- .../template/base/packages/client/_gitignore | 2 + .../with-pwa/packages/client/public/logo.png | Bin 0 -> 13390 bytes .../packages/client/pwa-assets.config.ts | 12 ++++++ .../with-pwa/packages/client/vite.config.ts | 35 ++++++++++++++++++ 11 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 .changeset/heavy-carpets-jump.md create mode 100644 apps/cli/template/with-pwa/packages/client/public/logo.png create mode 100644 apps/cli/template/with-pwa/packages/client/pwa-assets.config.ts create mode 100644 apps/cli/template/with-pwa/packages/client/vite.config.ts 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 0000000000000000000000000000000000000000..ac20b90200eb650d62ad37c7a1c866c24c266872 GIT binary patch literal 13390 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