chore: remove Firebase backend code, replaced by Supabase (see 2026-08-08 spec)

Deleted all Firebase-specific files and directories:
- Configuration: .firebaserc, firebase.json, firestore.* files, storage.rules
- Cloud Functions: functions/ directory (package.json, source, config)
- Tests: firebase-tests/, vitest.rules.config.ts
- Scripts: scripts/seed.ts
- Source: src/lib/firebase.ts and .test.ts
- Environment: .env.example

Removed dependencies:
- firebase (main package)
- firebase-admin, firebase-tools (dev)
- @firebase/rules-unit-testing (dev)

Removed scripts:
- test:rules (Firebase emulator tests)
- seed (Firestore seed script)

Updated package.json via npm install (729 packages removed)
Updated .gitignore (removed functions/ entries)
Updated vite.config.ts (removed firebase-tests and functions exclusions)

All tests, builds, and lints pass cleanly. No Firebase references remain in
the codebase (only in historical spec documents in docs/).

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
This commit is contained in:
belviskhoremk
2026-08-08 12:39:39 +00:00
parent 0dbdee9f95
commit 7d8d89d0fc
23 changed files with 6 additions and 14796 deletions

View File

@@ -1,6 +0,0 @@
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=

View File

@@ -1,5 +0,0 @@
{
"projects": {
"default": "demo-braids-by-mira"
}
}

4
.gitignore vendored
View File

@@ -12,10 +12,6 @@ dist
dist-ssr dist-ssr
*.local *.local
# Cloud Functions
functions/lib
functions/node_modules
# Environment variables # Environment variables
.env.local .env.local
.env.*.local .env.*.local

View File

@@ -1,39 +0,0 @@
# Phase 2A — Remaining work before this backend goes live
This backend (Firestore, Storage, Cloud Functions) was built entirely with placeholder
values because Task 1 of `docs/superpowers/plans/2026-08-04-phase2a-backend-foundation.md`
(manual Firebase/Resend account setup) hadn't been done yet. Before deploying for real:
## 1. Complete Task 1 of the plan
Create the Firebase project, upgrade to Blaze, enable Firestore/Auth/Storage,
create Mira's admin user, register a web app, create a Resend account + API key,
authenticate the Firebase CLI. Full instructions are in the plan's Task 1.
## 2. Replace these 4 placeholder values with real ones from Task 1
| Placeholder | Real value goes in | Also appears in |
|---|---|---|
| `demo-braids-by-mira` (fake project id) | `.firebaserc` | `package.json`'s `test:rules` script, `firebase-tests/firestore.rules.test.ts` |
| `dev-admin-placeholder-uid` (fake admin UID) | `firestore.rules`, `storage.rules` | `firebase-tests/firestore.rules.test.ts` |
| `bookings@braidsbymira.com` (placeholder from-address) | `functions/src/index.ts` (`FROM_ADDRESS`) | — must match a Resend-verified sending domain |
| `hello@braidsbymira.com` (placeholder Mira email) | `functions/src/index.ts` (`MIRA_EMAIL`) | — replace only if Mira's real notification inbox differs |
Also update `.env.local` (gitignored, not committed) with the real Firebase web app
config from Task 1, Step 5 — copy `.env.example` if you haven't already.
## 3. Run the one manual CLI step Task 6 skipped
```bash
npx firebase-tools functions:secrets:set RESEND_API_KEY
```
## 4. Run Task 8 of the plan
Deploy rules + functions, seed production data, and run the two live smoke tests
(create a real booking/inquiry document, confirm `bookedSlots` updates and email
arrives). Full steps are in the plan's Task 8.
## 5. Delete this file
Once all of the above is done and verified, delete this file — it's a temporary
tracking note, not permanent documentation.

View File

@@ -1,154 +0,0 @@
import { readFileSync } from 'node:fs'
import { describe, it, beforeAll, afterAll, beforeEach } from 'vitest'
import {
initializeTestEnvironment,
assertSucceeds,
assertFails,
type RulesTestEnvironment,
} from '@firebase/rules-unit-testing'
import { doc, getDoc, setDoc, addDoc, collection } from 'firebase/firestore'
const ADMIN_UID = 'dev-admin-placeholder-uid'
let testEnv: RulesTestEnvironment
beforeAll(async () => {
testEnv = await initializeTestEnvironment({
projectId: 'demo-braids-by-mira',
firestore: {
rules: readFileSync('firestore.rules', 'utf8'),
},
})
})
afterAll(async () => {
await testEnv.cleanup()
})
beforeEach(async () => {
await testEnv.clearFirestore()
})
describe('Firestore security rules', () => {
it('allows anyone to read products', async () => {
await testEnv.withSecurityRulesDisabled(async (ctx) => {
await setDoc(doc(ctx.firestore(), 'products/edge-control'), { name: 'Edge Control' })
})
const unauthed = testEnv.unauthenticatedContext()
await assertSucceeds(getDoc(doc(unauthed.firestore(), 'products/edge-control')))
})
it('blocks a non-admin from writing to products', async () => {
const unauthed = testEnv.unauthenticatedContext()
await assertFails(setDoc(doc(unauthed.firestore(), 'products/hacked'), { name: 'Hack' }))
})
it('allows the admin to write to products', async () => {
const admin = testEnv.authenticatedContext(ADMIN_UID)
await assertSucceeds(setDoc(doc(admin.firestore(), 'products/new-item'), { name: 'New Item' }))
})
it('allows anyone to create a booking', async () => {
const unauthed = testEnv.unauthenticatedContext()
await assertSucceeds(
addDoc(collection(unauthed.firestore(), 'bookings'), {
serviceId: 'knotless',
date: '2026-09-01',
time: '10:00 AM',
contact: { name: 'Jane', phone: '555-0100', email: 'jane@example.com', notes: '' },
status: 'confirmed',
}),
)
})
it('blocks creating a booking that is missing a required field', async () => {
const unauthed = testEnv.unauthenticatedContext()
await assertFails(
addDoc(collection(unauthed.firestore(), 'bookings'), {
serviceId: 'knotless',
date: '2026-09-01',
time: '10:00 AM',
status: 'confirmed',
// missing contact
}),
)
})
it('blocks creating a booking with a malformed date', async () => {
const unauthed = testEnv.unauthenticatedContext()
await assertFails(
addDoc(collection(unauthed.firestore(), 'bookings'), {
serviceId: 'knotless',
date: 'not-a-date',
time: '10:00 AM',
contact: { name: 'Jane', phone: '555-0100', email: 'jane@example.com', notes: '' },
status: 'confirmed',
}),
)
})
it('blocks a non-admin from reading bookings (PII protection)', async () => {
await testEnv.withSecurityRulesDisabled(async (ctx) => {
await setDoc(doc(ctx.firestore(), 'bookings/b1'), {
serviceId: 'knotless',
contact: { name: 'Jane', phone: '555-0100', email: 'jane@example.com', notes: '' },
})
})
const unauthed = testEnv.unauthenticatedContext()
await assertFails(getDoc(doc(unauthed.firestore(), 'bookings/b1')))
})
it('allows the admin to read bookings', async () => {
await testEnv.withSecurityRulesDisabled(async (ctx) => {
await setDoc(doc(ctx.firestore(), 'bookings/b1'), { serviceId: 'knotless' })
})
const admin = testEnv.authenticatedContext(ADMIN_UID)
await assertSucceeds(getDoc(doc(admin.firestore(), 'bookings/b1')))
})
it('allows anyone to create an inquiry with a valid shape', async () => {
const unauthed = testEnv.unauthenticatedContext()
await assertSucceeds(
addDoc(collection(unauthed.firestore(), 'inquiries'), {
productId: 'edge-control',
contact: { name: 'Jane', email: 'jane@example.com', notes: '' },
status: 'new',
}),
)
})
it('blocks creating an inquiry that is missing a required field', async () => {
const unauthed = testEnv.unauthenticatedContext()
await assertFails(
addDoc(collection(unauthed.firestore(), 'inquiries'), {
productId: 'edge-control',
status: 'new',
// missing contact
}),
)
})
it('blocks a non-admin from reading inquiries (PII protection)', async () => {
await testEnv.withSecurityRulesDisabled(async (ctx) => {
await setDoc(doc(ctx.firestore(), 'inquiries/i1'), {
productId: 'edge-control',
contact: { name: 'Jane', email: 'jane@example.com', notes: '' },
})
})
const unauthed = testEnv.unauthenticatedContext()
await assertFails(getDoc(doc(unauthed.firestore(), 'inquiries/i1')))
})
it('allows anyone to read bookedSlots (no PII there)', async () => {
await testEnv.withSecurityRulesDisabled(async (ctx) => {
await setDoc(doc(ctx.firestore(), 'bookedSlots/2026-09-01'), { times: ['10:00 AM'] })
})
const unauthed = testEnv.unauthenticatedContext()
await assertSucceeds(getDoc(doc(unauthed.firestore(), 'bookedSlots/2026-09-01')))
})
it('blocks a non-admin from writing bookedSlots directly', async () => {
const unauthed = testEnv.unauthenticatedContext()
await assertFails(setDoc(doc(unauthed.firestore(), 'bookedSlots/2026-09-01'), { times: ['1:00 PM'] }))
})
})

View File

@@ -1,23 +0,0 @@
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"storage": {
"rules": "storage.rules"
},
"functions": [
{
"source": "functions",
"codebase": "default",
"ignore": ["node_modules", ".git", "firebase-debug.log", "firebase-debug.*.log"]
}
],
"emulators": {
"auth": { "port": 9099 },
"functions": { "port": 5001 },
"firestore": { "port": 8080 },
"storage": { "port": 9199 },
"ui": { "enabled": true }
}
}

View File

@@ -1,4 +0,0 @@
{
"indexes": [],
"fieldOverrides": []
}

View File

@@ -1,53 +0,0 @@
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isAdmin() {
return request.auth != null && request.auth.uid == "dev-admin-placeholder-uid";
}
match /products/{productId} {
allow read: if true;
allow write: if isAdmin();
}
match /services/{serviceId} {
allow read: if true;
allow write: if isAdmin();
}
match /scheduleTemplate/{docId} {
allow read: if true;
allow write: if isAdmin();
}
match /scheduleExceptions/{date} {
allow read: if true;
allow write: if isAdmin();
}
match /bookedSlots/{date} {
allow read: if true;
allow write: if isAdmin();
}
match /bookings/{bookingId} {
allow create: if request.resource.data.keys().hasAll(['serviceId', 'date', 'time', 'contact', 'status'])
&& request.resource.data.date is string
&& request.resource.data.date.matches('^[0-9]{4}-[0-9]{2}-[0-9]{2}$')
&& request.resource.data.time is string
&& request.resource.data.contact is map
&& request.resource.data.contact.keys().hasAll(['name', 'phone', 'email', 'notes'])
&& request.resource.data.status is string;
allow read, update, delete: if isAdmin();
}
match /inquiries/{inquiryId} {
allow create: if request.resource.data.keys().hasAll(['productId', 'contact', 'status'])
&& request.resource.data.productId is string
&& request.resource.data.contact is map
&& request.resource.data.contact.keys().hasAll(['name', 'email', 'notes'])
&& request.resource.data.status is string;
allow read, update, delete: if isAdmin();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +0,0 @@
{
"name": "functions",
"private": true,
"type": "module",
"engines": { "node": "22" },
"main": "lib/index.js",
"scripts": {
"build": "tsc",
"test": "vitest run"
},
"dependencies": {
"firebase-admin": "^12.0.0",
"firebase-functions": "^5.0.0",
"resend": "^4.0.0"
},
"devDependencies": {
"typescript": "^5.6.0",
"vitest": "^4.1.10"
}
}

View File

@@ -1,53 +0,0 @@
import { describe, it, expect } from 'vitest'
import { buildBookingNotificationEmail, buildInquiryNotificationEmail } from './email.js'
describe('buildBookingNotificationEmail', () => {
it('includes the service, date, time, and contact details', () => {
const { subject, html } = buildBookingNotificationEmail({
serviceName: 'Knotless Braids',
date: '2026-09-01',
time: '10:00 AM',
contact: { name: 'Jane Doe', phone: '555-0100', email: 'jane@example.com', notes: 'First time client' },
})
expect(subject).toBe('New booking: Knotless Braids on 2026-09-01')
expect(html).toContain('Jane Doe')
expect(html).toContain('Knotless Braids')
expect(html).toContain('2026-09-01')
expect(html).toContain('10:00 AM')
expect(html).toContain('555-0100')
expect(html).toContain('jane@example.com')
expect(html).toContain('First time client')
})
it('omits the notes paragraph when notes are empty', () => {
const { html } = buildBookingNotificationEmail({
serviceName: 'Cornrows',
date: '2026-09-02',
time: '1:00 PM',
contact: { name: 'Amy', phone: '555-0101', email: 'amy@example.com', notes: '' },
})
expect(html).not.toContain('Notes:')
})
})
describe('buildInquiryNotificationEmail', () => {
it('includes the product and contact details', () => {
const { subject, html } = buildInquiryNotificationEmail({
productName: 'Silk Edge Control',
contact: { name: 'Nadia', email: 'nadia@example.com', notes: 'Is this in stock?' },
})
expect(subject).toBe('New inquiry: Silk Edge Control')
expect(html).toContain('Nadia')
expect(html).toContain('nadia@example.com')
expect(html).toContain('Silk Edge Control')
expect(html).toContain('Is this in stock?')
})
it('omits the notes paragraph when notes are empty', () => {
const { html } = buildInquiryNotificationEmail({
productName: 'Braid Oil',
contact: { name: 'Kim', email: 'kim@example.com', notes: '' },
})
expect(html).not.toContain('Notes:')
})
})

View File

@@ -1,28 +0,0 @@
export interface BookingEmailInput {
serviceName: string
date: string
time: string
contact: { name: string; phone: string; email: string; notes: string }
}
export function buildBookingNotificationEmail(input: BookingEmailInput): { subject: string; html: string } {
const notesLine = input.contact.notes ? `<p>Notes: ${input.contact.notes}</p>` : ''
return {
subject: `New booking: ${input.serviceName} on ${input.date}`,
html: `<p><strong>${input.contact.name}</strong> booked <strong>${input.serviceName}</strong> on ${input.date} at ${input.time}.</p>` +
`<p>Phone: ${input.contact.phone}<br/>Email: ${input.contact.email}</p>${notesLine}`,
}
}
export interface InquiryEmailInput {
productName: string
contact: { name: string; email: string; notes: string }
}
export function buildInquiryNotificationEmail(input: InquiryEmailInput): { subject: string; html: string } {
const notesLine = input.contact.notes ? `<p>Notes: ${input.contact.notes}</p>` : ''
return {
subject: `New inquiry: ${input.productName}`,
html: `<p><strong>${input.contact.name}</strong> (${input.contact.email}) asked about <strong>${input.productName}</strong>.</p>${notesLine}`,
}
}

View File

@@ -1,51 +0,0 @@
import { onDocumentCreated } from 'firebase-functions/v2/firestore'
import { defineSecret } from 'firebase-functions/params'
import { initializeApp } from 'firebase-admin/app'
import { getFirestore, FieldValue } from 'firebase-admin/firestore'
import { Resend } from 'resend'
import { buildBookingNotificationEmail, buildInquiryNotificationEmail } from './email.js'
initializeApp()
const resendApiKey = defineSecret('RESEND_API_KEY')
const FROM_ADDRESS = 'bookings@braidsbymira.com'
const MIRA_EMAIL = 'hello@braidsbymira.com'
export const onBookingCreated = onDocumentCreated(
{ document: 'bookings/{bookingId}', secrets: [resendApiKey] },
async (event) => {
const booking = event.data?.data()
if (!booking) return
if (typeof booking.date !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(booking.date)) return
if (typeof booking.time !== 'string' || booking.time.length === 0) return
const db = getFirestore()
await db
.doc(`bookedSlots/${booking.date}`)
.set({ times: FieldValue.arrayUnion(booking.time) }, { merge: true })
const resend = new Resend(resendApiKey.value())
const { subject, html } = buildBookingNotificationEmail({
serviceName: booking.serviceId,
date: booking.date,
time: booking.time,
contact: booking.contact,
})
await resend.emails.send({ from: FROM_ADDRESS, to: MIRA_EMAIL, subject, html })
},
)
export const onInquiryCreated = onDocumentCreated(
{ document: 'inquiries/{inquiryId}', secrets: [resendApiKey] },
async (event) => {
const inquiry = event.data?.data()
if (!inquiry) return
const resend = new Resend(resendApiKey.value())
const { subject, html } = buildInquiryNotificationEmail({
productName: inquiry.productId,
contact: inquiry.contact,
})
await resend.emails.send({ from: FROM_ADDRESS, to: MIRA_EMAIL, subject, html })
},
)

View File

@@ -1,13 +0,0 @@
{
"compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"target": "es2022",
"outDir": "lib",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}

View File

@@ -1,8 +0,0 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'node',
include: ['src/**/*.test.ts'],
},
})

9970
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,13 +8,10 @@
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview", "preview": "vite preview",
"test": "vitest run", "test": "vitest run"
"test:rules": "firebase emulators:exec --only firestore,storage --project demo-braids-by-mira \"vitest run --config vitest.rules.config.ts\"",
"seed": "tsx scripts/seed.ts"
}, },
"dependencies": { "dependencies": {
"@react-three/fiber": "^9.7.0", "@react-three/fiber": "^9.7.0",
"firebase": "^12.17.1",
"framer-motion": "^12.43.0", "framer-motion": "^12.43.0",
"react": "^19.2.8", "react": "^19.2.8",
"react-dom": "^19.2.8", "react-dom": "^19.2.8",
@@ -23,7 +20,6 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@firebase/rules-unit-testing": "^5.0.1",
"@testing-library/jest-dom": "^7.0.0", "@testing-library/jest-dom": "^7.0.0",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1", "@testing-library/user-event": "^14.6.1",
@@ -35,11 +31,8 @@
"eslint": "^10.8.0", "eslint": "^10.8.0",
"eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.3", "eslint-plugin-react-refresh": "^0.5.3",
"firebase-admin": "^14.2.0",
"firebase-tools": "^15.25.1",
"globals": "^17.7.0", "globals": "^17.7.0",
"jsdom": "^30.0.1", "jsdom": "^30.0.1",
"tsx": "^4.23.5",
"typescript": "~6.0.2", "typescript": "~6.0.2",
"typescript-eslint": "^8.65.0", "typescript-eslint": "^8.65.0",
"vite": "^8.2.0", "vite": "^8.2.0",

View File

@@ -1,40 +0,0 @@
import { initializeApp } from 'firebase-admin/app'
import { getFirestore } from 'firebase-admin/firestore'
import { products } from '../src/data/products.js'
import { services } from '../src/data/services.js'
initializeApp()
const db = getFirestore()
function toStoragePath(image: string): string {
return image.replace(/^\//, '')
}
async function seed() {
const batch = db.batch()
for (const product of products) {
const { id, image, ...rest } = product
batch.set(db.doc(`products/${id}`), { ...rest, image: toStoragePath(image) })
}
for (const service of services) {
const { id, image, ...rest } = service
batch.set(db.doc(`services/${id}`), { ...rest, image: toStoragePath(image) })
}
batch.set(db.doc('scheduleTemplate/default'), {
workingDays: [2, 3, 4, 5, 6],
startTime: '09:00',
endTime: '18:00',
slotMinutes: 120,
})
await batch.commit()
console.log(`Seeded ${products.length} products, ${services.length} services, and the default schedule.`)
}
seed().catch((err) => {
console.error(err)
process.exit(1)
})

View File

@@ -1,42 +0,0 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
const initializeAppMock = vi.fn(() => ({ name: 'mock-app' }))
const getFirestoreMock = vi.fn(() => ({ type: 'mock-firestore' }))
const getAuthMock = vi.fn(() => ({ type: 'mock-auth' }))
const getStorageMock = vi.fn(() => ({ type: 'mock-storage' }))
vi.mock('firebase/app', () => ({ initializeApp: initializeAppMock }))
vi.mock('firebase/firestore', () => ({ getFirestore: getFirestoreMock }))
vi.mock('firebase/auth', () => ({ getAuth: getAuthMock }))
vi.mock('firebase/storage', () => ({ getStorage: getStorageMock }))
describe('firebase client setup', () => {
beforeEach(() => {
vi.resetModules()
initializeAppMock.mockClear()
})
it('initializes the app with config read from Vite env vars and exports db/auth/storage', async () => {
vi.stubEnv('VITE_FIREBASE_API_KEY', 'test-api-key')
vi.stubEnv('VITE_FIREBASE_AUTH_DOMAIN', 'test.firebaseapp.com')
vi.stubEnv('VITE_FIREBASE_PROJECT_ID', 'test-project')
vi.stubEnv('VITE_FIREBASE_STORAGE_BUCKET', 'test-project.appspot.com')
vi.stubEnv('VITE_FIREBASE_MESSAGING_SENDER_ID', '123')
vi.stubEnv('VITE_FIREBASE_APP_ID', 'app-id')
const { app, db, auth, storage } = await import('./firebase')
expect(initializeAppMock).toHaveBeenCalledWith({
apiKey: 'test-api-key',
authDomain: 'test.firebaseapp.com',
projectId: 'test-project',
storageBucket: 'test-project.appspot.com',
messagingSenderId: '123',
appId: 'app-id',
})
expect(app).toEqual({ name: 'mock-app' })
expect(db).toEqual({ type: 'mock-firestore' })
expect(auth).toEqual({ type: 'mock-auth' })
expect(storage).toEqual({ type: 'mock-storage' })
})
})

View File

@@ -1,18 +0,0 @@
import { initializeApp } from 'firebase/app'
import { getFirestore } from 'firebase/firestore'
import { getAuth } from 'firebase/auth'
import { getStorage } from 'firebase/storage'
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,
}
export const app = initializeApp(firebaseConfig)
export const db = getFirestore(app)
export const auth = getAuth(app)
export const storage = getStorage(app)

View File

@@ -1,23 +0,0 @@
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
function isAdmin() {
return request.auth != null && request.auth.uid == "dev-admin-placeholder-uid";
}
match /products/{fileName} {
allow read: if true;
allow write: if isAdmin();
}
match /styles/{fileName} {
allow read: if true;
allow write: if isAdmin();
}
match /about/{fileName} {
allow read: if true;
allow write: if isAdmin();
}
}
}

View File

@@ -14,8 +14,6 @@ export default defineConfig({
'**/cypress/**', '**/cypress/**',
'**/.{idea,git,cache,output,temp}/**', '**/.{idea,git,cache,output,temp}/**',
'**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*', '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
'firebase-tests/**',
'functions/**',
], ],
}, },
}) })

View File

@@ -1,9 +0,0 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'node',
include: ['firebase-tests/**/*.test.ts'],
testTimeout: 20000,
},
})