Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
package-lock.json
.vscode
.vscode
yuv/
22 changes: 18 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { setupCSSFramework } from './lib/css-frameworks.js';
import { createAxiosSetup, createAppComponent, createPWAReadme } from './lib/templates.js';
import { setupRoutingFramework } from "./lib/router-setup.js";
import { initializeGit } from "./lib/setup-git.js";
import { setupFirebase } from "./lib/firebase.js";

const getExtraPackages = async (input) => {
if (!input) return []; //if no input, return empty array
Expand Down Expand Up @@ -44,18 +45,25 @@ const GITHUB_REPO_URL = "https://github.com/harshgupta20/quickstart-react/issues
})
const isPWA = await confirm({ message: "Do you want to make this a Progressive Web App (PWA)?", default: false });

const packages = await checkbox({
// Dedicated Firebase confirmation
const includeFirebase = await confirm({ message: "Do you want to setup Firebase in this project?", default: false });

let packages = await checkbox({
message: "Select optional packages:",
choices: [
{ name: "Axios", value: "axios" },
{ name: "React Icons", value: "react-icons" },
{ name: "React Hook Form", value: "react-hook-form" },
{ name: "Yup", value: "yup" },
{ name: "Formik", value: "formik" },
{ name: "Moment.js", value: "moment" }
{ name: "Moment.js", value: "moment" },
// Firebase now handled via dedicated confirmation above
]
});

// Merge firebase selection into a derived list for docs/readme purposes
const packagesWithFirebase = includeFirebase ? [...packages, 'firebase'] : [...packages];

const extraPackages = await selectPro({
message: 'Search extra packages to add',
multiple: true,
Expand Down Expand Up @@ -116,7 +124,8 @@ const GITHUB_REPO_URL = "https://github.com/harshgupta20/quickstart-react/issues
});

// 4. Install packages
const allPackages = [...routingPackages, ...packages, ...selectedExtraPackages];
const firebasePkg = includeFirebase ? ["firebase"] : [];
const allPackages = Array.from(new Set([...routingPackages, ...packages, ...selectedExtraPackages, ...firebasePkg]));
if (allPackages.length > 0) {
run(`npm install ${allPackages.join(" ")}`, projectPath);
if (config.devPackages.length > 0) {
Expand All @@ -137,6 +146,11 @@ const GITHUB_REPO_URL = "https://github.com/harshgupta20/quickstart-react/issues
createAxiosSetup(projectPath, isTS);
}

// 7b. Setup Firebase if selected
if (includeFirebase) {
setupFirebase(projectPath, isTS);
}

// 8. Clean up default boilerplate files
deleteFile(path.join(projectPath, "src", "App.css"));
if (cssFramework !== "Tailwind") {
Expand All @@ -148,7 +162,7 @@ const GITHUB_REPO_URL = "https://github.com/harshgupta20/quickstart-react/issues
setupRoutingFramework(projectPath, routingFramework, cssFramework, isTS);

// 10. Create comprehensive README
createPWAReadme(projectPath, projectName, cssFramework, packages, isPWA, isTS);
createPWAReadme(projectPath, projectName, cssFramework, packagesWithFirebase, isPWA, isTS);

// 11. Initialize Git repository
initializeGit(projectPath);
Expand Down
133 changes: 133 additions & 0 deletions lib/firebase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import path from 'path';
import { writeFile, readFile, fileExists, createFolder } from './utils.js';

const FIREBASE_ENV_KEYS = [
'VITE_FIREBASE_API_KEY',
'VITE_FIREBASE_AUTH_DOMAIN',
'VITE_FIREBASE_PROJECT_ID',
'VITE_FIREBASE_STORAGE_BUCKET',
'VITE_FIREBASE_MESSAGING_SENDER_ID',
'VITE_FIREBASE_APP_ID',
'VITE_FIREBASE_MEASUREMENT_ID',
];

const envBlock = () =>
FIREBASE_ENV_KEYS.map((k) => `${k}=`).join('\n') + '\n';

const ensureEnvFileHasFirebase = (filePath) => {
if (!fileExists(filePath)) {
writeFile(filePath, envBlock());
return;
}
const existing = readFile(filePath);
const missing = FIREBASE_ENV_KEYS.filter((k) => !new RegExp(`^${k}=`, 'm').test(existing));
if (missing.length === 0) return;
const appended = existing.endsWith('\n') ? existing : existing + '\n';
writeFile(filePath, appended + missing.map((k) => `${k}=`).join('\n') + '\n');
};

const createFirebaseUtil = (projectPath, isTS) => {
const utilsDir = path.join(projectPath, 'src', 'utils');
createFolder(utilsDir);

const tsContent = `import { initializeApp, getApps, getApp, FirebaseApp } from 'firebase/app';
import { getFirestore, Firestore, collection, doc, addDoc, getDoc, getDocs, updateDoc, deleteDoc, setDoc, DocumentData } from 'firebase/firestore';

const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY as string,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN as string,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID as string,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET as string,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID as string,
appId: import.meta.env.VITE_FIREBASE_APP_ID as string,
measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID as string,
};

export const app: FirebaseApp = getApps().length ? getApp() : initializeApp(firebaseConfig);
export const db: Firestore = getFirestore(app);

export async function createDoc<T extends DocumentData>(path: string, data: T, id?: string): Promise<string> {
if (id) {
await setDoc(doc(db, path, id), data);
return id;
}
const ref = await addDoc(collection(db, path), data);
return ref.id;
}

export async function readDocById<T>(path: string, id: string): Promise<T | null> {
const snapshot = await getDoc(doc(db, path, id));
return snapshot.exists() ? (snapshot.data() as T) : null;
}

export async function readCollection<T>(path: string): Promise<Array<{ id: string; data: T }>> {
const snap = await getDocs(collection(db, path));
return snap.docs.map((d) => ({ id: d.id, data: d.data() as T }));
}

export async function updateDocById<T>(path: string, id: string, data: Partial<T>): Promise<void> {
await updateDoc(doc(db, path, id), data as unknown as DocumentData);
}

export async function deleteDocById(path: string, id: string): Promise<void> {
await deleteDoc(doc(db, path, id));
}
`;

const jsContent = `import { initializeApp, getApps, getApp } from 'firebase/app';
import { getFirestore, collection, doc, addDoc, getDoc, getDocs, updateDoc, deleteDoc, setDoc } from 'firebase/firestore';

const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID,
};

export const app = getApps().length ? getApp() : initializeApp(firebaseConfig);
export const db = getFirestore(app);

export async function createDoc(path, data, id) {
if (id) {
await setDoc(doc(db, path, id), data);
return id;
}
const ref = await addDoc(collection(db, path), data);
return ref.id;
}

export async function readDocById(path, id) {
const snapshot = await getDoc(doc(db, path, id));
return snapshot.exists() ? snapshot.data() : null;
}

export async function readCollection(path) {
const snap = await getDocs(collection(db, path));
return snap.docs.map((d) => ({ id: d.id, data: d.data() }));
}

export async function updateDocById(path, id, data) {
await updateDoc(doc(db, path, id), data);
}

export async function deleteDocById(path, id) {
await deleteDoc(doc(db, path, id));
}
`;

writeFile(path.join(utilsDir, `firebase.${isTS ? 'ts' : 'js'}`), isTS ? tsContent : jsContent);
};

export const setupFirebase = (projectPath, isTS) => {
// 1. Create utils with CRUD helpers
createFirebaseUtil(projectPath, isTS);

// 2. Ensure env files contain firebase variables, values left blank
ensureEnvFileHasFirebase(path.join(projectPath, '.env'));
ensureEnvFileHasFirebase(path.join(projectPath, '.env.example'));

console.log('✅ Firebase initialized: utils and env variables added');
};
40 changes: 39 additions & 1 deletion lib/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,44 @@ ${isPWA ? `1. **Replace PWA Icons**: Replace SVG placeholders with proper PNG ic

Built using React + Vite${isPWA ? ' + PWA' : ''}
`;
const firebaseSection = packages.includes('firebase') ? `
## 🔥 Firebase

writeFile(path.join(projectPath, "README.md"), readmeContent);
Firebase SDK and Firestore helpers are available in \`src/utils/firebase.${isTS ? 'ts' : 'js'}\`.

### Env Variables
Create a \`.env\` (or use the generated \`.env.example\`) and fill these keys:
\`\`\`
VITE_FIREBASE_API_KEY=
VITE_FIREBASE_AUTH_DOMAIN=
VITE_FIREBASE_PROJECT_ID=
VITE_FIREBASE_STORAGE_BUCKET=
VITE_FIREBASE_MESSAGING_SENDER_ID=
VITE_FIREBASE_APP_ID=
VITE_FIREBASE_MEASUREMENT_ID=
\`\`\`

### Usage
\`\`\`${isTS ? 'ts' : 'js'}
import { db, createDoc, readDocById, readCollection, updateDocById, deleteDocById } from './utils/firebase';

// Create with auto-id
const id = await createDoc('users', { name: 'Ada', age: 30 });

// Read single doc
const user = await readDocById('users', id);

// Read collection
const users = await readCollection('users');

// Update
await updateDocById('users', id, { age: 31 });

// Delete
await deleteDocById('users', id);
\`\`\`
` : '';

const finalReadmeContent = readmeContent + firebaseSection;
writeFile(path.join(projectPath, "README.md"), finalReadmeContent);
};
7 changes: 6 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ npx quickstart-react
When you run `npx quickstart-react`, you will be prompted to:
1. **Enter Project Name** — e.g., `my-app`
2. **Choose CSS Framework** — Tailwind, Bootstrap, or MUI
3. **Select Optional Packages** — choose from a list of commonly used React libraries
3. **Choose Routing** — React Router or TanStack Router
4. **Enable PWA** — optional
5. **Setup Firebase** — simple Yes/No confirmation
6. **Select Other Optional Packages** — Axios, React Icons, React Hook Form, Yup, Formik, Moment.js, etc.

Example run:
```bash
Expand All @@ -32,6 +35,7 @@ npx quickstart-react
```
? Enter project name: my-portfolio
? Choose a CSS framework: Tailwind
? Setup Firebase in this project? Yes
? Select optional packages: Axios, React Icons
```

Expand Down Expand Up @@ -87,6 +91,7 @@ You can add these during setup:
- **Yup** — schema validation
- **Formik** — form management
- **Moment.js** — date/time utilities
- **Firebase** — added via a dedicated confirmation; installs the `firebase` package and generates Firestore utils (`src/utils/firebase.[ts|js]`) plus `.env` placeholders

## 🚀 Quick Start
```bash
Expand Down