easy-starter 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +32 -80
  2. package/bin/index.js +219 -44
  3. package/package.json +20 -11
  4. package/template/.cursorrules +23 -0
  5. package/template/.github/workflows/deploy.yml +3 -0
  6. package/template/README.md +81 -24
  7. package/template/env +3 -2
  8. package/template/gitignore +1 -1
  9. package/template/index.html +15 -0
  10. package/template/package.json +8 -9
  11. package/template/public/images/icon-16.png +0 -0
  12. package/template/public/images/icon-180.png +0 -0
  13. package/template/public/images/icon-192.png +0 -0
  14. package/template/public/images/icon-32.png +0 -0
  15. package/template/public/images/icon-512.png +0 -0
  16. package/template/{src → public}/site.webmanifest +6 -12
  17. package/template/scripts/deploy.ts +3 -2
  18. package/template/server/index.tsx +248 -61
  19. package/template/server/mail.tsx +120 -0
  20. package/template/src/App.tsx +33 -7
  21. package/template/src/Router.tsx +11 -2
  22. package/template/src/index.tsx +28 -1
  23. package/template/src/pages/Index.tsx +45 -6
  24. package/template/src/pages/ResetPassword.tsx +126 -0
  25. package/template/src/pages/admin/Admin.tsx +360 -85
  26. package/template/src/pages/admin/AnalyticsTab.tsx +71 -0
  27. package/template/src/pages/admin/ErrorsTab.tsx +46 -0
  28. package/template/src/pages/admin/UsersTab.tsx +61 -0
  29. package/template/src/services/api.ts +5 -7
  30. package/template/src/services/common.ts +23 -0
  31. package/template/src/styles.css +460 -0
  32. package/template/tsconfig.json +2 -1
  33. package/template/types.ts +44 -3
  34. package/template/vite.config.ts +24 -0
  35. package/template/.parcelrc +0 -6
  36. package/template/src/images/icon.png +0 -0
  37. package/template/src/index.html +0 -16
  38. package/template/src/styles.scss +0 -292
package/README.md CHANGED
@@ -1,33 +1,35 @@
1
1
  # easy-starter 🚀
2
2
 
3
- A lightweight CLI tool to bootstrap modern fullstack React web projects.
3
+ A lightweight CLI tool to bootstrap modern fullstack React web projects with Vite v8, TypeScript 7.0, and optional authentication.
4
4
 
5
5
  ```bash
6
6
  npx easy-starter [your-project-name]
7
7
  ```
8
8
 
9
- **Keywords:** React, Server-Side Rendering (SSR), Database (DB), Analytics, Type-Safe API, Fullstack, Minimalist
9
+ **Keywords:** React, Server-Side Rendering (SSR), Vite, Database (DB), Analytics, Type-Safe API, User Authentication, Fullstack, Minimalist
10
10
 
11
11
  Tired of rigid frameworks forcing you into complex folder structures and steep learning curves? **easy-starter** is built on the philosophy of maximum freedom and modularity. It provides a solid foundation by combining a set of carefully curated, simple, and powerful tools without locking you into a monolithic architecture like Next.js or Remix.
12
12
 
13
- ## Why easy-starter? (The Philosophy)
13
+ ---
14
14
 
15
- Modern full-stack frameworks are incredibly powerful, but they often come with significant trade-offs: strict file-system routing rules, immense complexity under the hood, and the modern paradigm of strictly dividing components into "client" and "server" boundaries.
15
+ ## What's New in v2.0.0?
16
16
 
17
- **easy-starter solves this by giving you back control.**
17
+ * **Vite v8 Bundler:** Switched from Parcel to Vite v8, offering lightning-fast HMR and seamless dev proxying.
18
+ * **TypeScript 7.0:** Fully upgraded dependencies and compiler options to support TypeScript 7.
19
+ * **Interactive Installer:** Run `npx easy-starter` and configure exactly what you need. Zero bloat — files, comments, and package dependencies are completely omitted if a feature is disabled.
20
+ * **User Authentication:** Out-of-the-box integration with `easy-user-auth` including secure cookie rotation, user registration/login modals, role checking, and custom password recovery emails.
21
+ * **Dynamic Social Previews:** Generate dynamic `og:image` screenshots of your pages automatically using headless browser rendering (`puppeteer`) and cached locally in `easy-db-node`.
22
+ * **Flexible Viewport Tuning:** Optional script injection to scale mobile layouts to a standard viewport width (min-width: 600px).
18
23
 
19
- * **No Server/Client Component Split:** Forget about adding `'use client'` everywhere or stressing about what runs where. With `easy-starter`, you just write standard React components. The integrated `ssr-hook` elegantly handles data fetching on the server and seamless hydration on the client for you.
20
- * **No rigid structure:** You decide where your components, pages, and server logic live.
21
- * **Clear Backend/Frontend separation:** Server code stays on the Express server, client code stays in React, connected by a seamlessly typed RPC API.
22
- * **Minimalist dependencies:** We rely on simple, purpose-built libraries instead of heavy abstractions.
23
- * **Instant Productivity:** You get SSR, a Database, Analytics, and a Type-Safe API out of the box in seconds.
24
+ ---
24
25
 
25
26
  ## What's included in the box?
26
27
 
27
- The generated boilerplate comes pre-configured with a stack designed for developer happiness and raw performance:
28
+ The generated boilerplate can be configured to contain:
28
29
 
29
- * **Frontend/Backend:** React 19, TypeScript, and Express.
30
- * **Bundler:** Parcel - zero-configuration, lightning-fast builds.
30
+ * **Frontend/Backend:** React 19, TypeScript 7.0, and Express.
31
+ * **Bundler:** Vite v8 - lightning-fast client bundles with native dev proxying.
32
+ * **User Authentication:** [`easy-user-auth`](https://www.npmjs.com/package/easy-user-auth) - Highly secure password-recovery, registration, and cookie rotation framework.
31
33
  * **Server-Side Rendering (SSR):** [`ssr-hook`](https://www.npmjs.com/package/ssr-hook) - Dead-simple SSR for React. Fetches data on the server and hydrates on the client without the flash of loading content.
32
34
  * **Type-Safe API:** [`typed-client-server-api`](https://www.npmjs.com/package/typed-client-server-api) - An RPC-like interface giving you full TypeScript autocomplete and safety between your Express server and React client.
33
35
  * **Database:** [`easy-db-node`](https://www.npmjs.com/package/easy-db-node) - A simple, local JSON-based database perfect for small to medium projects where setting up Postgres/MongoDB is overkill.
@@ -36,83 +38,31 @@ The generated boilerplate comes pre-configured with a stack designed for develop
36
38
  * **AI-Friendly (.cursorrules):** Pre-configured, LLM-optimized guidelines in the project root so AI coding assistants (Cursor, Windsurf, Copilot, etc.) immediately understand the codebase conventions, API restrictions, routing patterns, and database operations.
37
39
  * **Deployment:** Integrated Github Actions workflow and PM2 deployment script for seamless, automated deployment straight to your own VPS.
38
40
 
39
- ## Core Concepts & Examples
41
+ ---
40
42
 
41
- > **Note:** `easy-starter` gives you a solid foundation, but you are not forced into any of these patterns. If you prefer a different routing library (like `react-router`), another data fetching solution (like `react-query` or `swr`), or standard REST endpoints, you are completely free to swap them out and use your own libraries!
43
+ ## Interactive Configuration Prompts
42
44
 
43
- ### 1. Defining your API
45
+ When you run `npx easy-starter`, the CLI will prompt you with the following configuration questions:
44
46
 
45
- Your API contract is defined in a single file (`types.d.ts`). This ensures your frontend and backend are always perfectly in sync.
47
+ 1. **Project name:** Choose the name of the folder and the app.
48
+ 2. **User Authentication:** Do you want to add user login/registration widgets? (Installs `easy-user-auth` and nodemailer, generates JWT secrets in `.env`).
49
+ 3. **Viewport Width Fix:** Do you want to fix iOS mobile viewport issues by ensuring a minimum width of 600px? (Adds JS viewport resizing listener and sets CSS `min-width`).
50
+ 4. **Admin Dashboard:** Do you want to include `/admin` page? (If Auth is enabled, protects it using user roles. If Auth is disabled, protects it using a static password).
51
+ 5. **Analytics:** Do you want local visitor analytics? (Installs `easy-analytics`, adds dashboard visualization).
52
+ 6. **SSR Rendering:** Do you want Server-Side Rendering? (Installs `ssr-hook`, configures hydration).
53
+ 7. **og:image Generation:** Do you want dynamic `og:image` generation? (Installs `puppeteer` to automatically screenshot URLs for social cards, cached via easy-db).
54
+ 8. **Deployment Script:** Do you want to deploy to your VPS using GitHub Actions? (Installs `node-ssh`, adds SSH deploy script).
46
55
 
47
- ```typescript
48
- // types.d.ts
49
- import { APIDefinition, Endpoint } from "typed-client-server-api";
50
-
51
- export type API = APIDefinition<{
52
- // Define an endpoint: parameters -> response
53
- getItems: Endpoint<{}, Item[]>;
54
- addItem: Endpoint<{ name: string; value: number }, string>;
55
- }>;
56
- ```
57
-
58
- On the server, you implement these endpoints:
59
-
60
- ```typescript
61
- // server/index.tsx
62
- import { initApi } from "typed-client-server-api";
63
- import { API } from "../types";
64
-
65
- initApi<API>(app, {
66
- getItems: async () => {
67
- return db.select("item");
68
- },
69
- addItem: async ({ name, value }) => {
70
- return db.insert("item", { name, value });
71
- }
72
- });
73
- ```
74
-
75
- ### 2. Fetching Data with SSR (`useSSRApi`)
76
-
77
- To fetch data on the server during SSR and seamlessly hydrate it on the client without loading spinners, use `useSSRApi` in your React components.
78
-
79
- ```tsx
80
- // src/pages/Index.tsx
81
- import React from "react";
82
- import { useSSRApi } from "../services/api";
83
-
84
- export default function Index() {
85
- // This will run on the server, fetch the data, embed it in the HTML,
86
- // and hydrate perfectly on the client. Fully type-safe!
87
- const [items, error, isLoading, reload] = useSSRApi.getItems({});
88
-
89
- if (isLoading) return <p>Loading...</p>;
90
- if (error) return <p>Error: {error.message}</p>;
91
-
92
- return (
93
- <ul>
94
- {items?.map(item => <li key={item._id}>{item.name}</li>)}
95
- </ul>
96
- );
97
- }
98
- ```
99
-
100
- ### 3. AI Integration (`.cursorrules`)
101
-
102
- This template includes a pre-configured `.cursorrules` file in the root. If you develop using AI assistants such as Cursor, Windsurf, or GitHub Copilot, they will automatically read this file to understand the specific conventions of `easy-starter`.
56
+ ---
103
57
 
104
58
  ## Getting Started
105
59
 
106
- Bootstrapping a new project is as simple as running one command:
60
+ Bootstrapping a new project is as simple as running:
107
61
 
108
62
  ```bash
109
63
  npx easy-starter [your-project-name]
110
64
  ```
111
65
 
112
- ### Interactive CLI
113
-
114
- If you run `npx easy-starter` without a project name, the CLI will guide you interactively. It will also ask if you want to include a ready-to-use **Deployment Script** (`scripts/deploy.ts` and GitHub Actions workflow) utilizing `node-ssh` and PM2 for dead-simple automated deployments to your VPS.
115
-
116
66
  ### Running your project
117
67
 
118
68
  Once created, navigate to your new project folder and start the development server:
@@ -122,13 +72,15 @@ cd your-project-name
122
72
  npm run dev
123
73
  ```
124
74
 
125
- This command runs both the Express backend and the Parcel frontend bundler concurrently. Your app will be live at `http://localhost:1234`!
75
+ This command starts both the Express backend on port `1111` and the Vite dev server on port `5173`. Your app will be live at `http://localhost:5173`!
76
+
77
+ ---
126
78
 
127
79
  ## Available Scripts
128
80
 
129
81
  Inside your newly generated project, you can use the following scripts:
130
82
 
131
- * `npm run dev`: Starts the development server with hot-reloading for both backend and frontend.
83
+ * `npm run dev`: Starts the development server with hot-reloading. Open `http://localhost:5173`.
132
84
  * `npm run build`: Builds the project for production.
133
85
  * `npm start`: Starts the production server (make sure to build first).
134
86
  * `npm run deploy`: (Optional) Executes the `scripts/deploy.ts` script to deploy your built project via SSH and PM2.
package/bin/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const readline = require('readline');
6
+ const crypto = require('crypto');
6
7
  const { execSync } = require('child_process');
7
8
 
8
9
  const rl = readline.createInterface({
@@ -13,34 +14,95 @@ const rl = readline.createInterface({
13
14
  const askQuestion = (query) => new Promise(resolve => rl.question(query, resolve));
14
15
 
15
16
  async function main() {
16
- console.log('🚀 Welcome to easy-starter!');
17
+ console.log('🚀 Welcome to easy-starter v2.0.0!');
17
18
 
18
19
  let projectName = process.argv[2];
19
20
  if (!projectName) {
20
21
  projectName = await askQuestion('Enter the project name (e.g., my-awesome-app): ');
21
22
  }
23
+ projectName = projectName.trim();
22
24
 
23
25
  if (!projectName) {
24
- console.error('Project name cannot be empty!');
26
+ console.error('Project name cannot be empty!');
25
27
  rl.close();
26
28
  process.exit(1);
27
29
  }
28
30
 
29
- const useAdminDashboard = await askQuestion('Do you want to include an admin dashboard for analytics? (y/n) [y]: ');
30
- const includeAdmin = useAdminDashboard.toLowerCase() !== 'n';
31
+ console.log('\n🔒 1. User Authentication & Login (easy-user-auth)');
32
+ console.log(' Provides out-of-the-box user registration, login dialogs, secure cookie-based');
33
+ console.log(' sessions, and forgotten password recovery emails.');
34
+ const ansAuth = await askQuestion(' Do you want to include user authentication? (y/n) [y]: ');
35
+ const useAuth = ansAuth.trim().toLowerCase() !== 'n';
31
36
 
32
- console.log('\n🚢 Deployment (GitHub Actions, VPS, SSH, PM2)');
33
- console.log('You can automatically include a deployment script designed for GitHub Actions.');
34
- console.log('It connects to your VPS via SSH, uploads your project, builds it, and runs it via PM2.');
35
- const useDeployScript = await askQuestion('Do you want to include this GitHub Actions deploy script? (y/n) [y]: ');
36
- const includeDeploy = useDeployScript.toLowerCase() !== 'n';
37
+ console.log('\n📊 2. Analytics Tracking (easy-analytics)');
38
+ console.log(' Tracks page visits, referrers, resolutions, and browsers locally without cookies.');
39
+ console.log(' Also captures and records client-side/frontend errors encountered by users.');
40
+ console.log(' If the Admin Dashboard is enabled, it generates interactive visual graphs inside /admin.');
41
+ const ansAnalytics = await askQuestion(' Do you want to include privacy-friendly analytics? (y/n) [y]: ');
42
+ const useAnalytics = ansAnalytics.trim().toLowerCase() !== 'n';
43
+
44
+ console.log('\n🛠️ 3. Admin Dashboard (/admin)');
45
+ console.log(' A secure section to manage your app. Protected by user authentication roles');
46
+ console.log(' (admin/user) if auth is enabled, or a static environment password if disabled.');
47
+ const ansAdmin = await askQuestion(' Do you want to include the admin dashboard? (y/n) [y]: ');
48
+ const useAdmin = ansAdmin.trim().toLowerCase() !== 'n';
49
+
50
+ let useAdminUsers = false;
51
+ let useAdminErrors = false;
52
+
53
+ if (useAdmin) {
54
+ if (useAuth) {
55
+ console.log('\n👥 3a. Admin User Management');
56
+ console.log(' Allows administrators to list registered users and change their roles');
57
+ console.log(' directly from the admin dashboard.');
58
+ const ansAdminUsers = await askQuestion(' Do you want to include User Management in the admin panel? (y/n) [y]: ');
59
+ useAdminUsers = ansAdminUsers.trim().toLowerCase() !== 'n';
60
+ } else {
61
+ console.log('\nℹ️ Note: User Management is disabled in the admin panel because it requires User Authentication.');
62
+ }
63
+
64
+ if (useAnalytics) {
65
+ console.log('\n🚨 3b. Admin Error Logging');
66
+ console.log(' Captures frontend/client-side errors (requires easy-analytics) and lists them');
67
+ console.log(' inside the admin panel, including descriptions and collapsible stack traces.');
68
+ const ansAdminErrors = await askQuestion(' Do you want to include Error Logging in the admin panel? (y/n) [y]: ');
69
+ useAdminErrors = ansAdminErrors.trim().toLowerCase() !== 'n';
70
+ } else {
71
+ console.log('\nℹ️ Note: Error Logging is disabled in the admin panel because it requires');
72
+ console.log(' Analytics Tracking to be enabled.');
73
+ }
74
+ }
75
+
76
+ console.log('\n⚡ 4. Server-Side Rendering (ssr-hook)');
77
+ console.log(' Enables server-side pre-rendering for React. Enhances initial load speeds,');
78
+ console.log(' prevents content flashes, and optimizes your site for search engine crawlers (SEO).');
79
+ const ansSSR = await askQuestion(' Do you want to enable Server-Side Rendering (SSR)? (y/n) [y]: ');
80
+ const useSSR = ansSSR.trim().toLowerCase() !== 'n';
81
+
82
+ console.log('\n🖼️ 5. Social Preview og:image');
83
+ console.log(' Automatically screenshots and generates social preview card og:images for your pages');
84
+ console.log(' using a headless browser rendering engine (puppeteer) and caches them in the database.');
85
+ const ansOgImage = await askQuestion(' Do you want to include dynamic og:image generation? (y/n) [y]: ');
86
+ const useOgImage = ansOgImage.trim().toLowerCase() !== 'n';
87
+
88
+ console.log('\n📱 6. Viewport Tuning');
89
+ console.log(' Injects a viewport event listener that forces a minimum width layout (600px)');
90
+ console.log(' for mobile devices, resolving scaling/rendering issues on iOS Safari.');
91
+ const ansViewport = await askQuestion(' Do you want to fix iOS Safari viewport width issues? (y/n) [y]: ');
92
+ const useViewport = ansViewport.trim().toLowerCase() !== 'n';
93
+
94
+ console.log('\n🚢 7. Deployment Integration (node-ssh)');
95
+ console.log(' Configures deploy scripts and GitHub Actions workflows to build and transfer your code');
96
+ console.log(' via SSH straight to your own VPS (Virtual Private Server), where it runs managed by PM2.');
97
+ const ansDeploy = await askQuestion(' Do you want to include VPS deploy scripts? (y/n) [y]: ');
98
+ const useDeploy = ansDeploy.trim().toLowerCase() !== 'n';
37
99
 
38
100
  rl.close();
39
101
 
40
102
  const targetPath = path.join(process.cwd(), projectName);
41
103
 
42
104
  if (fs.existsSync(targetPath)) {
43
- console.error(`Folder ${projectName} already exists!`);
105
+ console.error(`❌ Folder ${projectName} already exists!`);
44
106
  process.exit(1);
45
107
  }
46
108
 
@@ -49,6 +111,20 @@ async function main() {
49
111
 
50
112
  const templatePath = path.join(__dirname, '..', 'template');
51
113
 
114
+ // Ignored files based on configuration
115
+ const ignoreList = ['env'];
116
+ if (!useDeploy) {
117
+ ignoreList.push('scripts');
118
+ ignoreList.push('.github');
119
+ }
120
+ if (!useAuth) {
121
+ ignoreList.push('mail.tsx');
122
+ ignoreList.push('ResetPassword.tsx');
123
+ }
124
+ if (!useAdmin) {
125
+ ignoreList.push('admin');
126
+ }
127
+
52
128
  function copyFolderSync(from, to, ignoreFiles = []) {
53
129
  fs.mkdirSync(to, { recursive: true });
54
130
  fs.readdirSync(from).forEach(element => {
@@ -64,78 +140,177 @@ async function main() {
64
140
  });
65
141
  }
66
142
 
67
- const ignoreList = includeDeploy ? [] : ['scripts', '.github'];
68
143
  copyFolderSync(templatePath, targetPath, ignoreList);
69
144
 
70
- // Rename safe files back to dotfiles
71
- if (fs.existsSync(path.join(targetPath, 'env'))) {
72
- fs.renameSync(path.join(targetPath, 'env'), path.join(targetPath, '.env'));
73
- }
145
+ // Rename gitignore to .gitignore
74
146
  if (fs.existsSync(path.join(targetPath, 'gitignore'))) {
75
147
  fs.renameSync(path.join(targetPath, 'gitignore'), path.join(targetPath, '.gitignore'));
76
148
  }
77
149
 
78
- // Update site.webmanifest
79
- const manifestPath = path.join(targetPath, 'src', 'site.webmanifest');
150
+ // Update site.webmanifest name
151
+ const manifestPath = path.join(targetPath, 'public', 'site.webmanifest');
80
152
  if (fs.existsSync(manifestPath)) {
81
153
  let manifestContent = fs.readFileSync(manifestPath, 'utf-8');
82
154
  manifestContent = manifestContent.replace(/__NAME__/g, projectName);
83
155
  fs.writeFileSync(manifestPath, manifestContent);
84
156
  }
85
157
 
86
- // Handle Admin section removal if the user doesn't want it
87
- if (!includeAdmin) {
88
- const adminPath = path.join(targetPath, 'src', 'pages', 'admin');
89
- if (fs.existsSync(adminPath)) {
90
- fs.rmSync(adminPath, { recursive: true, force: true });
158
+ // Helper for processing feature block comments recursively
159
+ const startRegex = (blockName) => new RegExp(`(?:\\/\\/|\\/\\*|<!--)\\s*---\\s*${blockName}\\s*START\\s*---\\s*(?:\\*\\/|-->)?\\r?\\n?`, 'g');
160
+ const endRegex = (blockName) => new RegExp(`(?:\\/\\/|\\/\\*|<!--)\\s*---\\s*${blockName}\\s*END\\s*---\\s*(?:\\*\\/|-->)?\\r?\\n?`, 'g');
161
+ const fullRegex = (blockName) => new RegExp(`(?:\\/\\/|\\/\\*|<!--)\\s*---\\s*${blockName}\\s*START\\s*---\\s*(?:\\*\\/|-->)?\\r?\\n?[\\s\\S]*?(?:\\/\\/|\\/\\*|<!--)\\s*---\\s*${blockName}\\s*END\\s*---\\s*(?:\\*\\/|-->)?\\r?\\n?`, 'g');
162
+
163
+ function handleBlock(content, name, enabled) {
164
+ if (enabled) {
165
+ return content.replace(startRegex(name), '').replace(endRegex(name), '');
166
+ } else {
167
+ return content.replace(fullRegex(name), '');
91
168
  }
169
+ }
92
170
 
93
- // Strip // --- ADMIN START --- ... // --- ADMIN END --- blocks
94
- const stripAdminBlocks = (filePath) => {
95
- if (fs.existsSync(filePath)) {
96
- let content = fs.readFileSync(filePath, 'utf-8');
97
- content = content.replace(/\/\/ --- ADMIN START ---[\s\S]*?\/\/ --- ADMIN END ---\n?/g, '');
98
- fs.writeFileSync(filePath, content);
99
- }
100
- };
171
+ function processBlocks(content) {
172
+ content = handleBlock(content, 'AUTH', useAuth);
173
+ content = handleBlock(content, 'NO_AUTH', !useAuth);
174
+
175
+ content = handleBlock(content, 'VIEWPORT', useViewport);
176
+
177
+ content = handleBlock(content, 'ADMIN', useAdmin);
178
+ content = handleBlock(content, 'NO_ADMIN', !useAdmin);
179
+
180
+ if (useAdmin) {
181
+ content = handleBlock(content, 'ADMIN_WITH_AUTH', useAuth);
182
+ content = handleBlock(content, 'ADMIN_NO_AUTH', !useAuth);
183
+
184
+ content = handleBlock(content, 'ADMIN_USERS', useAuth && useAdminUsers);
185
+ content = handleBlock(content, 'NO_ADMIN_USERS', !(useAuth && useAdminUsers));
186
+
187
+ content = handleBlock(content, 'ADMIN_ERRORS', useAdminErrors);
188
+ content = handleBlock(content, 'NO_ADMIN_ERRORS', !useAdminErrors);
189
+
190
+ content = handleBlock(content, 'ADMIN_ERRORS_WITH_AUTH', useAdminErrors && useAuth);
191
+ content = handleBlock(content, 'ADMIN_ERRORS_NO_AUTH', useAdminErrors && !useAuth);
192
+ } else {
193
+ content = handleBlock(content, 'ADMIN_WITH_AUTH', false);
194
+ content = handleBlock(content, 'ADMIN_NO_AUTH', false);
195
+
196
+ content = handleBlock(content, 'ADMIN_USERS', false);
197
+ content = handleBlock(content, 'NO_ADMIN_USERS', true);
198
+
199
+ content = handleBlock(content, 'ADMIN_ERRORS', false);
200
+ content = handleBlock(content, 'NO_ADMIN_ERRORS', true);
101
201
 
102
- stripAdminBlocks(path.join(targetPath, 'src', 'Router.tsx'));
103
- stripAdminBlocks(path.join(targetPath, 'server', 'index.tsx'));
104
- stripAdminBlocks(path.join(targetPath, 'src', 'styles.scss'));
202
+ content = handleBlock(content, 'ADMIN_ERRORS_WITH_AUTH', false);
203
+ content = handleBlock(content, 'ADMIN_ERRORS_NO_AUTH', false);
204
+ }
205
+
206
+ content = handleBlock(content, 'ANALYTICS', useAnalytics);
207
+ content = handleBlock(content, 'NO_ANALYTICS', !useAnalytics);
208
+
209
+ content = handleBlock(content, 'SSR', useSSR);
210
+ content = handleBlock(content, 'NO_SSR', !useSSR);
211
+
212
+ content = handleBlock(content, 'OGIMAGE', useOgImage);
213
+
214
+ return content;
105
215
  }
106
216
 
107
- // Update package.json from template
217
+ function processDirectory(directory) {
218
+ fs.readdirSync(directory).forEach(element => {
219
+ const fullPath = path.join(directory, element);
220
+ const stat = fs.lstatSync(fullPath);
221
+ if (stat.isDirectory()) {
222
+ processDirectory(fullPath);
223
+ } else if (stat.isFile()) {
224
+ const ext = path.extname(element);
225
+ const textExtensions = ['.ts', '.tsx', '.js', '.jsx', '.css', '.html', '.cursorrules', '.webmanifest', 'README.md'];
226
+ if (textExtensions.includes(ext) || element === '.cursorrules' || element === 'README.md') {
227
+ let fileContent = fs.readFileSync(fullPath, 'utf-8');
228
+ fileContent = processBlocks(fileContent);
229
+ fileContent = fileContent.replace(/__NAME__/g, projectName);
230
+ fs.writeFileSync(fullPath, fileContent);
231
+ }
232
+ }
233
+ });
234
+ }
235
+
236
+ processDirectory(targetPath);
237
+
238
+ // Generate dynamic package.json
108
239
  const packageJsonPath = path.join(targetPath, 'package.json');
109
240
  if (fs.existsSync(packageJsonPath)) {
110
241
  const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
111
242
  pkg.name = projectName;
112
243
 
113
- if (includeDeploy) {
114
- pkg.devDependencies = pkg.devDependencies || {};
244
+ if (useAnalytics) {
245
+ pkg.dependencies['easy-analytics'] = '^1.0.1';
246
+ }
247
+ if (useAuth) {
248
+ pkg.dependencies['easy-user-auth'] = '^1.0.6';
249
+ pkg.dependencies['nodemailer'] = '^7.0.9';
250
+ pkg.devDependencies['@types/nodemailer'] = '^6.4.15';
251
+ }
252
+ if (useSSR) {
253
+ pkg.dependencies['ssr-hook'] = '^0.2.0';
254
+ }
255
+ if (useOgImage) {
256
+ pkg.dependencies['puppeteer'] = '^25.3.0';
257
+ }
258
+ if (useDeploy) {
115
259
  pkg.devDependencies['node-ssh'] = '^13.2.1';
116
260
  pkg.scripts = pkg.scripts || {};
117
261
  pkg.scripts['deploy'] = 'tsx scripts/deploy.ts';
118
- } else if (pkg.scripts && pkg.scripts.deploy) {
119
- delete pkg.scripts.deploy;
262
+ } else {
263
+ if (pkg.scripts && pkg.scripts.deploy) {
264
+ delete pkg.scripts.deploy;
265
+ }
120
266
  }
121
267
 
122
268
  fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
123
269
  }
124
270
 
271
+ // Generate dynamic .env file
272
+ const envPath = path.join(targetPath, '.env');
273
+ let envContent = `SERVER_PORT=1111\nSERVER_URL=https://${projectName}.com\n`;
274
+
275
+ if (useAdmin && !useAuth) {
276
+ envContent += `ADMIN_PASSWORD=admin123\n`;
277
+ }
278
+
279
+ if (useAuth) {
280
+ const jwtSecret = crypto.randomBytes(32).toString('hex');
281
+ envContent += `\n# Authentication\nJWT_SECRET=${jwtSecret}\n`;
282
+ envContent += `# SMTP mail configuration (required for forgotten password recovery)\nMAIL_SMTP=smtp.example.com\nMAIL_PORT=587\nMAIL_USER=your-email@example.com\nMAIL_PASSWORD=your-email-password\n`;
283
+ }
284
+
285
+ fs.writeFileSync(envPath, envContent);
286
+
125
287
  console.log('📦 Installing dependencies (this may take a while)...');
126
288
  try {
127
289
  execSync('npm install', { cwd: targetPath, stdio: 'inherit' });
128
290
  } catch (error) {
129
- console.error('Failed to install dependencies.', error);
130
- process.exit(1);
291
+ console.warn('\n⚠️ Failed to install dependencies with default npm install (often due to Puppeteer browser download issue).');
292
+ console.log('Retrying with PUPPETEER_SKIP_DOWNLOAD=1...');
293
+ try {
294
+ execSync('npm install', { cwd: targetPath, stdio: 'inherit', env: { ...process.env, PUPPETEER_SKIP_DOWNLOAD: '1' } });
295
+ } catch (retryError) {
296
+ console.error('❌ Failed to install dependencies on retry.', retryError);
297
+ process.exit(1);
298
+ }
131
299
  }
132
300
 
133
301
  console.log(`\n🎉 Project ${projectName} has been successfully created!`);
134
- console.log('\nGetting started:');
302
+ console.log('\n🚀 Getting started:');
135
303
  console.log(` cd ${projectName}`);
136
- console.log(' npm run dev -> http://localhost:1234');
137
- if (includeDeploy) {
138
- console.log(' npm run deploy');
304
+ console.log(' npm run dev (Starts backend and Vite frontend dev server at http://localhost:5173)');
305
+
306
+ if (useAuth) {
307
+ console.log('\n⚠️ IMPORTANT FOR AUTHENTICATION:');
308
+ console.log(' Configure your SMTP settings inside the generated .env file');
309
+ console.log(' to enable password recovery email delivery.');
310
+ }
311
+
312
+ if (useDeploy) {
313
+ console.log(' npm run deploy (Deploy to your VPS)');
139
314
  }
140
315
  }
141
316
 
package/package.json CHANGED
@@ -1,24 +1,33 @@
1
1
  {
2
2
  "name": "easy-starter",
3
- "version": "1.2.0",
4
- "description": "A lightweight CLI tool to bootstrap modern fullstack React web projects.",
3
+ "version": "2.0.0",
4
+ "description": "A lightweight, production-ready fullstack React boilerplate generator with Vite v8, TypeScript 7, server-side rendering (SSR), local analytics, SSH deploy script, PM2, and secure user authentication.",
5
5
  "bin": {
6
6
  "easy-starter": "./bin/index.js"
7
7
  },
8
8
  "keywords": [
9
9
  "cli",
10
- "bootstrap",
11
- "starter",
12
- "react",
13
- "easy",
10
+ "boilerplate",
11
+ "starter-kit",
12
+ "react-starter",
13
+ "vite-template",
14
+ "typescript-boilerplate",
15
+ "fullstack-boilerplate",
16
+ "server-side-rendering",
14
17
  "ssr",
15
- "typescript",
16
- "fullstack",
18
+ "user-authentication",
19
+ "user-login",
20
+ "easy-user-auth",
21
+ "easy-analytics",
17
22
  "easy-db",
18
- "parcel",
19
- "ai",
23
+ "local-database",
24
+ "pm2-deployment",
25
+ "vps-deploy",
26
+ "ssh-deployment",
27
+ "ai-friendly",
20
28
  "cursorrules",
21
- "ai-friendly"
29
+ "quickstart",
30
+ "zero-config"
22
31
  ],
23
32
  "author": "Filip Paulů <ing.fenix@seznam.cz>",
24
33
  "license": "MIT",
@@ -90,3 +90,26 @@ export type Database = {
90
90
  // CORRECT (custom ID):
91
91
  await update("item", "explicit-custom-id", { name: "Custom ID Item", value: 99 });
92
92
  ```
93
+
94
+ ---
95
+
96
+ ## 4. Styling & Image Manipulation Guidelines
97
+
98
+ ### CSS Implementation
99
+ * **Centralized & Generic**: Implement as many styles as possible in `src/styles.css` using generic, reusable selectors.
100
+ * **Minimize Inline Styles**: Avoid inline styles (e.g., `style={{ ... }}`) in React components. Keep style declarations in the central CSS file to maintain design consistency and code quality.
101
+
102
+ ### Image Asset Handling
103
+ * **Use `vite-imagetools` Exclusively in CSS:**
104
+ - When loading local images for styling and background layouts, reference them exclusively inside CSS `url()` declarations, appending `vite-imagetools` query parameters (such as `format=webp` and dimensions `w=`) to automatically optimize them.
105
+ - **CORRECT (CSS):**
106
+ ```css
107
+ .hero-banner {
108
+ background-image: url("./assets/hero.jpg?format=webp&w=1200");
109
+ }
110
+ ```
111
+ * **JavaScript Imports for Images are Forbidden:**
112
+ - Do **NOT** import image assets inside `.js`, `.ts`, or `.tsx` files (e.g. do NOT write `import hero from "./hero.jpg"`).
113
+ * **Referencing Images in TSX/React (Only via `/public`):**
114
+ - If you need to reference or display an image directly inside React TSX components (e.g., standard `<img>` tags or SEO metadata), **you must place the pre-optimized WebP image inside the `/public` directory** and reference it using a standard absolute URL path.
115
+ - **CORRECT (TSX):** `<img src="/images/logo.webp" alt="Logo" />` (with the file stored inside `/public/images/logo.webp`).
@@ -26,4 +26,7 @@ jobs:
26
26
  SSH_HOST: ${{ secrets.SSH_HOST }}
27
27
  SSH_USERNAME: ${{ secrets.SSH_USERNAME }}
28
28
  SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }}
29
+ SSH_PATH: ${{ secrets.SSH_PATH }}
30
+ PM2_APP_NAME: ${{ secrets.PM2_APP_NAME }}
31
+ SERVER_URL: ${{ secrets.SERVER_URL }}
29
32
  run: npm run deploy