Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
27 changes: 24 additions & 3 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,32 @@ 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({
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" },
{ name: "Firebase (Firestore utils + env)", value: "firebase" }
]
});

// Fallback: some terminals or clients may not toggle checkboxes reliably.
// If Firebase wasn't picked above, ask a simple confirm so the user can't miss it.
if (!packages.includes('firebase')) {
try {
const firebaseConfirm = await confirm({ message: 'Would you like to add Firebase (Firestore utils + env)?', default: false });
if (firebaseConfirm) {
packages = [...packages, 'firebase'];
}
} catch (e) {
// ignore confirm errors and continue
}
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a dedicated confirmation command for firebase, which ask,
do you want to setup firebase in this project?

If yes then then just simply add it in packages and rest is setup.

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

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

// 7b. Setup Firebase if selected
if (packages.includes("firebase")) {
setupFirebase(projectPath, isTS);
}

// 8. Clean up default boilerplate files
deleteFile(path.join(projectPath, "src", "App.css"));
if (cssFramework !== "Tailwind") {
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);
};