diff --git a/.prettierrc b/.prettierrc index 4b173ec..6366838 100644 --- a/.prettierrc +++ b/.prettierrc @@ -6,4 +6,4 @@ "tabWidth": 2, "useTabs": false, "endOfLine": "auto" -} \ No newline at end of file +} diff --git a/README.md b/README.md index f1caa9f..7282775 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Flyer Crawler is a web application that uses the Google Gemini AI to extract, analyze, and manage data from grocery store flyers. Users can upload flyer images or PDFs, and the application will automatically identify items, prices, and sale dates, storing the structured data in a PostgreSQL database for historical analysis, price tracking, and personalized deal alerts. -We are working on an app to help people save money, by finding good deals that are only advertized in store flyers/ads. So, the primary purpose of the site is to make uploading flyers as easy as possible and as accurate as possible, and to store peoples needs, so sales can be matched to needs. +We are working on an app to help people save money, by finding good deals that are only advertized in store flyers/ads. So, the primary purpose of the site is to make uploading flyers as easy as possible and as accurate as possible, and to store peoples needs, so sales can be matched to needs. ## Features @@ -45,9 +45,9 @@ This project is configured to run in a CI/CD environment and does not use `.env` 1. **Set up a PostgreSQL database instance.** 2. **Run the Database Schema**: - - Connect to your database using a tool like `psql` or DBeaver. - - Open `sql/schema.sql.txt`, copy its entire contents, and execute it against your database. - - This will create all necessary tables, functions, and relationships. + - Connect to your database using a tool like `psql` or DBeaver. + - Open `sql/schema.sql.txt`, copy its entire contents, and execute it against your database. + - This will create all necessary tables, functions, and relationships. ### Step 2: Install Dependencies and Run the Application @@ -79,11 +79,11 @@ sudo nano /etc/nginx/mime.types change -application/javascript js; +application/javascript js; TO -application/javascript js mjs; +application/javascript js mjs; RESTART NGINX @@ -95,7 +95,7 @@ actually the proper change was to do this in the /etc/nginx/sites-available/flye ## for OAuth 1. Get Google OAuth Credentials -This is a crucial step that you must do outside the codebase: + This is a crucial step that you must do outside the codebase: Go to the Google Cloud Console. @@ -112,7 +112,7 @@ Under Authorized redirect URIs, click ADD URI and enter the URL where Google wil Click Create. You will be given a Client ID and a Client Secret. 2. Get GitHub OAuth Credentials -You'll need to obtain a Client ID and Client Secret from GitHub: + You'll need to obtain a Client ID and Client Secret from GitHub: Go to your GitHub profile settings. @@ -133,21 +133,23 @@ You will be given a Client ID and a Client Secret. psql -h localhost -U flyer_crawler_user -d "flyer-crawler-prod" -W - ## postgis flyer-crawler-prod=> SELECT version(); - version ------------------------------------------------------------------------------------------------------------------------------------------- - PostgreSQL 14.19 (Ubuntu 14.19-0ubuntu0.22.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04.2) 11.4.0, 64-bit +version + +--- + +PostgreSQL 14.19 (Ubuntu 14.19-0ubuntu0.22.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04.2) 11.4.0, 64-bit (1 row) flyer-crawler-prod=> SELECT PostGIS_Full_Version(); - postgis_full_version --------------------------------------------------------------------------------------------------------------------------------------------------------------------- - POSTGIS="3.2.0 c3e3cc0" [EXTENSION] PGSQL="140" GEOS="3.10.2-CAPI-1.16.0" PROJ="8.2.1" LIBXML="2.9.12" LIBJSON="0.15" LIBPROTOBUF="1.3.3" WAGYU="0.5.0 (Internal)" -(1 row) +postgis_full_version +--- + +POSTGIS="3.2.0 c3e3cc0" [EXTENSION] PGSQL="140" GEOS="3.10.2-CAPI-1.16.0" PROJ="8.2.1" LIBXML="2.9.12" LIBJSON="0.15" LIBPROTOBUF="1.3.3" WAGYU="0.5.0 (Internal)" +(1 row) ## production postgres setup @@ -201,9 +203,13 @@ Step 4: Seed the Admin Account (If Needed) Your application has a separate script to create the initial admin user. To run it, you must first set the required environment variables in your shell session. bash + # Set variables for the current session + export DB_USER=flyer_crawler_user DB_PASSWORD=your_password DB_NAME="flyer-crawler-prod" ... + # Run the seeding script + npx tsx src/db/seed_admin_account.ts Your production database is now ready! @@ -284,8 +290,6 @@ Test Execution: Your tests run against this clean, isolated schema. This approach is faster, more reliable, and removes the need for sudo access within the CI pipeline. - - gitea-runner@projectium:~$ pm2 install pm2-logrotate [PM2][Module] Installing NPM pm2-logrotate module [PM2][Module] Calling [NPM] to install pm2-logrotate ... @@ -293,7 +297,7 @@ gitea-runner@projectium:~$ pm2 install pm2-logrotate added 161 packages in 5s 21 packages are looking for funding - run `npm fund` for details +run `npm fund` for details npm notice npm notice New patch version of npm available! 11.6.3 -> 11.6.4 npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.6.4 @@ -308,23 +312,23 @@ $ pm2 set pm2-logrotate:retain 30 $ pm2 set pm2-logrotate:compress false $ pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss $ pm2 set pm2-logrotate:workerInterval 30 -$ pm2 set pm2-logrotate:rotateInterval 0 0 * * * +$ pm2 set pm2-logrotate:rotateInterval 0 0 \* \* _ $ pm2 set pm2-logrotate:rotateModule true Modules configuration. Copy/Paste line to edit values. [PM2][Module] Module successfully installed and launched [PM2][Module] Checkout module options: `$ pm2 conf` ┌────┬───────────────────────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ -│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ +│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ ├────┼───────────────────────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ -│ 2 │ flyer-crawler-analytics-worker │ default │ 0.0.0 │ fork │ 3846981 │ 7m │ 5 │ online │ 0% │ 55.8mb │ git… │ disabled │ -│ 11 │ flyer-crawler-api │ default │ 0.0.0 │ fork │ 3846987 │ 7m │ 0 │ online │ 0% │ 59.0mb │ git… │ disabled │ -│ 12 │ flyer-crawler-worker │ default │ 0.0.0 │ fork │ 3846988 │ 7m │ 0 │ online │ 0% │ 54.2mb │ git… │ disabled │ +│ 2 │ flyer-crawler-analytics-worker │ default │ 0.0.0 │ fork │ 3846981 │ 7m │ 5 │ online │ 0% │ 55.8mb │ git… │ disabled │ +│ 11 │ flyer-crawler-api │ default │ 0.0.0 │ fork │ 3846987 │ 7m │ 0 │ online │ 0% │ 59.0mb │ git… │ disabled │ +│ 12 │ flyer-crawler-worker │ default │ 0.0.0 │ fork │ 3846988 │ 7m │ 0 │ online │ 0% │ 54.2mb │ git… │ disabled │ └────┴───────────────────────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘ Module ┌────┬──────────────────────────────┬───────────────┬──────────┬──────────┬──────┬──────────┬──────────┬──────────┐ -│ id │ module │ version │ pid │ status │ ↺ │ cpu │ mem │ user │ +│ id │ module │ version │ pid │ status │ ↺ │ cpu │ mem │ user │ ├────┼──────────────────────────────┼───────────────┼──────────┼──────────┼──────┼──────────┼──────────┼──────────┤ -│ 13 │ pm2-logrotate │ 3.0.0 │ 3848878 │ online │ 0 │ 0% │ 20.1mb │ git… │ +│ 13 │ pm2-logrotate │ 3.0.0 │ 3848878 │ online │ 0 │ 0% │ 20.1mb │ git… │ └────┴──────────────────────────────┴───────────────┴──────────┴──────────┴──────┴──────────┴──────────┴──────────┘ gitea-runner@projectium:~$ pm2 set pm2-logrotate:max_size 10M [PM2] Module pm2-logrotate restarted @@ -335,7 +339,7 @@ $ pm2 set pm2-logrotate:retain 30 $ pm2 set pm2-logrotate:compress false $ pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss $ pm2 set pm2-logrotate:workerInterval 30 -$ pm2 set pm2-logrotate:rotateInterval 0 0 * * * +$ pm2 set pm2-logrotate:rotateInterval 0 0 _ \* _ $ pm2 set pm2-logrotate:rotateModule true gitea-runner@projectium:~$ pm2 set pm2-logrotate:retain 14 [PM2] Module pm2-logrotate restarted @@ -346,33 +350,31 @@ $ pm2 set pm2-logrotate:retain 14 $ pm2 set pm2-logrotate:compress false $ pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss $ pm2 set pm2-logrotate:workerInterval 30 -$ pm2 set pm2-logrotate:rotateInterval 0 0 * * * +$ pm2 set pm2-logrotate:rotateInterval 0 0 _ \* \* $ pm2 set pm2-logrotate:rotateModule true gitea-runner@projectium:~$ - - - ## dev server setup: Here are the steps to set up the development environment on Windows using Podman with an Ubuntu container: 1. Install Prerequisites on Windows -Install WSL 2: Podman on Windows relies on the Windows Subsystem for Linux. Install it by running wsl --install in an administrator PowerShell. -Install Podman Desktop: Download and install Podman Desktop for Windows. + Install WSL 2: Podman on Windows relies on the Windows Subsystem for Linux. Install it by running wsl --install in an administrator PowerShell. + Install Podman Desktop: Download and install Podman Desktop for Windows. 2. Set Up Podman -Initialize Podman: Launch Podman Desktop. It will automatically set up its WSL 2 machine. -Start Podman: Ensure the Podman machine is running from the Podman Desktop interface. + Initialize Podman: Launch Podman Desktop. It will automatically set up its WSL 2 machine. + Start Podman: Ensure the Podman machine is running from the Podman Desktop interface. 3. Set Up the Ubuntu Container - - Pull Ubuntu Image: Open a PowerShell or command prompt and pull the latest Ubuntu image: - podman pull ubuntu:latest - - Create a Podman Volume: Create a volume to persist node_modules and avoid installing them every time the container starts. - podman volume create node_modules_cache - - Run the Ubuntu Container: Start a new container with the project directory mounted and the necessary ports forwarded. - - Open a terminal in your project's root directory on Windows. - - Run the following command, replacing D:\gitea\flyer-crawler.projectium.com\flyer-crawler.projectium.com with the full path to your project: + +- Pull Ubuntu Image: Open a PowerShell or command prompt and pull the latest Ubuntu image: + podman pull ubuntu:latest +- Create a Podman Volume: Create a volume to persist node_modules and avoid installing them every time the container starts. + podman volume create node_modules_cache +- Run the Ubuntu Container: Start a new container with the project directory mounted and the necessary ports forwarded. + - Open a terminal in your project's root directory on Windows. + - Run the following command, replacing D:\gitea\flyer-crawler.projectium.com\flyer-crawler.projectium.com with the full path to your project: podman run -it -p 3001:3001 -p 5173:5173 --name flyer-dev -v "D:\gitea\flyer-crawler.projectium.com\flyer-crawler.projectium.com:/app" -v "node_modules_cache:/app/node_modules" ubuntu:latest @@ -383,46 +385,40 @@ podman run -it -p 3001:3001 -p 5173:5173 --name flyer-dev -v "D:\gitea\flyer-cra -v "node_modules_cache:/app/node_modules": Mounts the named volume for node_modules. 4. Configure the Ubuntu Environment - You are now inside the Ubuntu container's shell. + You are now inside the Ubuntu container's shell. - - Update Package Lists: - apt-get update - - Install Dependencies: Install curl, git, and nodejs (which includes npm). - apt-get install -y curl git - curl -sL https://deb.nodesource.com/setup_20.x | bash - - apt-get install -y nodejs - - Navigate to Project Directory: - cd /app +- Update Package Lists: + apt-get update +- Install Dependencies: Install curl, git, and nodejs (which includes npm). + apt-get install -y curl git + curl -sL https://deb.nodesource.com/setup_20.x | bash - + apt-get install -y nodejs +- Navigate to Project Directory: + cd /app - - Install Project Dependencies: - npm install +- Install Project Dependencies: + npm install 5. Run the Development Server - Start the Application: - npm run dev + npm run dev 6. Accessing the Application - - Frontend: Open your browser and go to http://localhost:5173. - - Backend: The frontend will make API calls to http://localhost:3001. + +- Frontend: Open your browser and go to http://localhost:5173. +- Backend: The frontend will make API calls to http://localhost:3001. Managing the Environment - - Stopping the Container: Press Ctrl+C in the container terminal, then type exit. - - Restarting the Container: - podman start -a -i flyer-dev - +- Stopping the Container: Press Ctrl+C in the container terminal, then type exit. +- Restarting the Container: + podman start -a -i flyer-dev ## for me: cd /mnt/d/gitea/flyer-crawler.projectium.com/flyer-crawler.projectium.com podman run -it -p 3001:3001 -p 5173:5173 --name flyer-dev -v "$(pwd):/app" -v "node_modules_cache:/app/node_modules" ubuntu:latest - - - - - - rate limiting - respect the AI service's rate limits, making it more stable and robust. You can adjust the GEMINI_RPM environment variable in your production environment as needed without changing the code. \ No newline at end of file + respect the AI service's rate limits, making it more stable and robust. You can adjust the GEMINI_RPM environment variable in your production environment as needed without changing the code. diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index d5f3327..4fbac83 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -15,7 +15,7 @@ module.exports = { args: 'server.ts', // tsx will execute this file // Production Environment Settings env_production: { - NODE_ENV: 'production', // Set the Node.js environment to production + NODE_ENV: 'production', // Set the Node.js environment to production name: 'flyer-crawler-api', cwd: '/var/www/flyer-crawler.projectium.com', }, @@ -63,4 +63,4 @@ module.exports = { }, }, ], -}; \ No newline at end of file +}; diff --git a/eslint.config.js b/eslint.config.js index d6bfd8d..cf596d8 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,21 +1,21 @@ -import globals from "globals"; -import tseslint from "typescript-eslint"; -import pluginReact from "eslint-plugin-react"; -import pluginReactHooks from "eslint-plugin-react-hooks"; -import pluginReactRefresh from "eslint-plugin-react-refresh"; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; +import pluginReact from 'eslint-plugin-react'; +import pluginReactHooks from 'eslint-plugin-react-hooks'; +import pluginReactRefresh from 'eslint-plugin-react-refresh'; export default tseslint.config( { // Global ignores - ignores: ["dist", ".gitea", "node_modules", "*.cjs"], + ignores: ['dist', '.gitea', 'node_modules', '*.cjs'], }, { // All files - files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], + files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'], plugins: { react: pluginReact, - "react-hooks": pluginReactHooks, - "react-refresh": pluginReactRefresh, + 'react-hooks': pluginReactHooks, + 'react-refresh': pluginReactRefresh, }, languageOptions: { globals: { @@ -24,12 +24,9 @@ export default tseslint.config( }, }, rules: { - "react-refresh/only-export-components": [ - "warn", - { allowConstantExport: true }, - ], + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], }, }, // TypeScript files ...tseslint.configs.recommended, -); \ No newline at end of file +); diff --git a/express.d.ts b/express.d.ts index 38b0171..0783f0f 100644 --- a/express.d.ts +++ b/express.d.ts @@ -1,4 +1,4 @@ -// src/types/express.d.ts +// express.d.ts import { Logger } from 'pino'; /** @@ -12,4 +12,4 @@ declare global { log: Logger; } } -} \ No newline at end of file +} diff --git a/index.html b/index.html index bca9cae..0d9ba34 100644 --- a/index.html +++ b/index.html @@ -1,20 +1,20 @@ - + - - - - Grocery Flyer AI Analyzer - - - - -
- - - - \ No newline at end of file + + + + Grocery Flyer AI Analyzer + + + + +
+ + + + diff --git a/metadata.json b/metadata.json index cedc0a4..8601284 100644 --- a/metadata.json +++ b/metadata.json @@ -1,8 +1,5 @@ { "name": "Flyer Crawler", "description": "Upload a grocery store flyer image to extract item details, prices, and quantities using AI. Get insights, meal plans, and compare prices to save money on your shopping.", - "requestFramePermissions": [ - "geolocation", - "microphone" - ] -} \ No newline at end of file + "requestFramePermissions": ["geolocation", "microphone"] +} diff --git a/postcss.config.js b/postcss.config.js index 60be969..e43dba0 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -10,10 +10,13 @@ const tailwindConfigPath = path.resolve(process.cwd(), 'tailwind.config.js'); console.log(`[POSTCSS] Attempting to use Tailwind config at: ${tailwindConfigPath}`); // Log to prove the imported config object is what we expect -console.log('[POSTCSS] Imported tailwind.config.js object:', JSON.stringify(tailwindConfig, null, 2)); +console.log( + '[POSTCSS] Imported tailwind.config.js object:', + JSON.stringify(tailwindConfig, null, 2), +); export default { plugins: { '@tailwindcss/postcss': {}, // The empty object is correct. }, -}; \ No newline at end of file +}; diff --git a/src/config.ts b/src/config.ts index 39c77eb..d78cfaa 100644 --- a/src/config.ts +++ b/src/config.ts @@ -16,4 +16,4 @@ const config = { }, }; -export default config; \ No newline at end of file +export default config; diff --git a/src/index.css b/src/index.css index c55165a..160f67d 100644 --- a/src/index.css +++ b/src/index.css @@ -4,7 +4,7 @@ This single directive replaces @tailwind base, components, and utilities. It is the new entry point for all of Tailwind's generated CSS. */ -@import "tailwindcss"; +@import 'tailwindcss'; /* This is the new v4 directive that tells the @tailwindcss/postcss plugin @@ -12,4 +12,3 @@ Since tailwind.config.js is in the root and this is in src/, the path is '../tailwind.config.js'. */ @config '../tailwind.config.js'; - diff --git a/src/index.tsx b/src/index.tsx index 840973a..832ed94 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -8,17 +8,16 @@ import './index.css'; const rootElement = document.getElementById('root'); if (!rootElement) { - throw new Error("Could not find root element to mount to"); + throw new Error('Could not find root element to mount to'); } const root = ReactDOM.createRoot(rootElement); root.render( - + - - + + , ); - \ No newline at end of file diff --git a/src/lib/toast.ts b/src/lib/toast.ts index 0a6da61..cefb8f4 100644 --- a/src/lib/toast.ts +++ b/src/lib/toast.ts @@ -5,4 +5,4 @@ import toast from 'react-hot-toast'; // This intermediate file allows us to mock 'src/lib/toast' reliably in tests // without wrestling with the internal structure of the 'react-hot-toast' package. export * from 'react-hot-toast'; -export default toast; \ No newline at end of file +export default toast; diff --git a/src/types.ts b/src/types.ts index ef6b77d..9d7d92c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -52,7 +52,10 @@ export type FlyerDbInsert = Omit & { store_id: number * Represents the data required to insert a new flyer item into the database. * It's a subset of the full FlyerItem type. */ -export type FlyerItemInsert = Omit; +export type FlyerItemInsert = Omit< + FlyerItem, + 'flyer_item_id' | 'flyer_id' | 'created_at' | 'updated_at' +>; export interface UnitPrice { value: number; @@ -163,13 +166,12 @@ export interface Profile { updated_by?: string | null; } - /** * Represents the combined user and profile data object returned by the backend's /users/profile endpoint. * It embeds the User object within the Profile object. * It also includes the full Address object if one is associated with the profile. */ -export type UserProfile = Profile & { +export type UserProfile = Profile & { user: User; address?: Address | null; }; @@ -325,7 +327,6 @@ export interface RecipeIngredientSubstitution { notes?: string | null; } - export interface Tag { tag_id: number; name: string; @@ -718,7 +719,10 @@ export type AiAnalysisAction = // Dispatched when an analysis that returns a simple string succeeds. | { type: 'FETCH_SUCCESS_TEXT'; payload: { analysisType: AnalysisType; data: string } } // Dispatched when an analysis that returns text and sources succeeds. - | { type: 'FETCH_SUCCESS_GROUNDED'; payload: { analysisType: AnalysisType; data: GroundedResponse } } + | { + type: 'FETCH_SUCCESS_GROUNDED'; + payload: { analysisType: AnalysisType; data: GroundedResponse }; + } // Dispatched when the image generation succeeds. | { type: 'FETCH_SUCCESS_IMAGE'; payload: { data: string } } // Dispatched when any analysis fails. @@ -738,11 +742,25 @@ export interface ProcessingStage { } export const CATEGORIES = [ - 'Fruits & Vegetables', 'Meat & Seafood', 'Dairy & Eggs', 'Bakery & Bread', - 'Pantry & Dry Goods', 'Beverages', 'Frozen Foods', 'Snacks', 'Household & Cleaning', - 'Personal Care & Health', 'Baby & Child', 'Pet Supplies', 'Deli & Prepared Foods', - 'Canned Goods', 'Condiments & Spices', 'Breakfast & Cereal', 'Organic', - 'International Foods', 'Other/Miscellaneous' + 'Fruits & Vegetables', + 'Meat & Seafood', + 'Dairy & Eggs', + 'Bakery & Bread', + 'Pantry & Dry Goods', + 'Beverages', + 'Frozen Foods', + 'Snacks', + 'Household & Cleaning', + 'Personal Care & Health', + 'Baby & Child', + 'Pet Supplies', + 'Deli & Prepared Foods', + 'Canned Goods', + 'Condiments & Spices', + 'Breakfast & Cereal', + 'Organic', + 'International Foods', + 'Other/Miscellaneous', ]; /** @@ -750,11 +768,11 @@ export const CATEGORIES = [ * This is the structure returned from the backend to the frontend. */ export interface ExtractedCoreData { - store_name: string; - valid_from: string | null; - valid_to: string | null; - store_address: string | null; - items: ExtractedFlyerItem[]; + store_name: string; + valid_from: string | null; + valid_to: string | null; + store_address: string | null; + items: ExtractedFlyerItem[]; } /** @@ -776,19 +794,19 @@ export interface ExtractedFlyerItem { * Represents the logo data extracted from a flyer by the AI service. */ export interface ExtractedLogoData { - store_logo_base_64: string | null; + store_logo_base_64: string | null; } /** * Represents the data extracted from a receipt image by the AI service. */ export interface ExtractedReceiptData { - raw_text: string; - items: { - raw_item_description: string; - quantity: number; - price_paid_cents: number; - }[]; + raw_text: string; + items: { + raw_item_description: string; + quantity: number; + price_paid_cents: number; + }[]; } /** @@ -930,10 +948,10 @@ export interface LeaderboardUser { * This is a public-facing type and does not include sensitive fields. */ export interface AdminUserView { - user_id: string; - email: string; - created_at: string; - role: 'admin' | 'user'; - full_name: string | null; - avatar_url: string | null; -} \ No newline at end of file + user_id: string; + email: string; + created_at: string; + role: 'admin' | 'user'; + full_name: string | null; + avatar_url: string | null; +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index c782248..9ab038f 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -10,4 +10,4 @@ interface ImportMetaEnv { interface ImportMeta { readonly env: ImportMetaEnv; -} \ No newline at end of file +} diff --git a/src/vitest.setup.ts b/src/vitest.setup.ts index fb84886..562434f 100644 --- a/src/vitest.setup.ts +++ b/src/vitest.setup.ts @@ -2,4 +2,4 @@ // This file can be used for global setup logic that applies to ALL test projects // defined in the workspace. Since our unit and integration tests have distinct -// setup requirements, this file is currently empty. \ No newline at end of file +// setup requirements, this file is currently empty. diff --git a/tailwind.config.js b/tailwind.config.js index b942dfc..f6b96cf 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -4,8 +4,5 @@ console.log('--- [EXECUTION PROOF] tailwind.config.js is being loaded. ---'); /** @type {import('tailwindcss').Config} */ export default { - content: [ - "./index.html", - "./src/**/*.{js,ts,jsx,tsx}", - ], -}; \ No newline at end of file + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], +}; diff --git a/tsconfig.json b/tsconfig.json index f8e8871..182b955 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,12 +17,10 @@ "jsx": "react-jsx", // This line makes Vitest's global APIs (describe, it, expect) available everywhere // without needing to import them. - "types": [ - "vitest/globals" - ] + "types": ["vitest/globals"] }, // This is the most important part: It tells TypeScript to include ALL files // within the 'src' directory, including your new 'vite-env.d.ts' file. "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] -} \ No newline at end of file +} diff --git a/tsconfig.node.json b/tsconfig.node.json index 18a5a2c..33efa59 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -9,5 +9,10 @@ "strict": true, // It's good practice to keep tooling config strict "types": ["node"] }, - "include": ["vite.config.ts", "vitest.config.ts", "vitest.config.integration.ts", "vitest.workspace.ts"] -} \ No newline at end of file + "include": [ + "vite.config.ts", + "vitest.config.ts", + "vitest.config.integration.ts", + "vitest.workspace.ts" + ] +} diff --git a/vite.config.ts b/vite.config.ts index cf8e899..26ccdd6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -22,7 +22,7 @@ export default defineConfig({ resolve: { alias: { // Use __dirname for a more robust path resolution - '@': path.resolve(__dirname, './src'), + '@': path.resolve(__dirname, './src'), // This alias ensures that any import of 'services/logger' is resolved // to the browser-safe client version during the Vite build process. // Server-side code should explicitly import 'services/logger.server'. @@ -51,7 +51,7 @@ export default defineConfig({ '**/node_modules/**', '**/dist/**', 'src/tests/integration/**', // Exclude the entire integration test directory - '**/*.e2e.test.ts' + '**/*.e2e.test.ts', ], // Disable file parallelism to run tests sequentially (replaces --no-threads) fileParallelism: false, @@ -60,10 +60,12 @@ export default defineConfig({ reporter: [ // Add maxCols to suggest a wider output for the text summary. ['text', { maxCols: 200 }], - 'html', 'json'], + 'html', + 'json', + ], // hanging-process reporter helps identify tests that do not exit properly - comes at a high cost tho //reporter: ['verbose', 'html', 'json', 'hanging-process'], - reportsDirectory: './.coverage/unit', + reportsDirectory: './.coverage/unit', clean: true, reportOnFailure: true, // This ensures the report generates even if tests fail include: ['src/**/*.{ts,tsx}'], @@ -71,19 +73,19 @@ export default defineConfig({ // By excluding scripts, setup files, and type definitions, we get a more accurate // picture of the test coverage for the actual application logic. exclude: [ - 'src/index.tsx', // Application entry point - 'src/main.tsx', // A common alternative entry point name + 'src/index.tsx', // Application entry point + 'src/main.tsx', // A common alternative entry point name 'src/types.ts', 'src/tests/**', // Exclude all test setup and helper files - 'src/vitest.setup.ts', // Global test setup config + 'src/vitest.setup.ts', // Global test setup config 'src/**/*.test.{ts,tsx}', // Exclude test files themselves 'src/**/*.stories.{ts,tsx}', // Exclude Storybook stories 'src/**/*.d.ts', // Exclude type definition files 'src/components/icons/**', // Exclude icon components if they are simple wrappers - 'src/db/seed.ts', // Database seeding script - 'src/db/seed_admin_account.ts', // Database seeding script - 'src/db/backup_user.ts', // Database backup script + 'src/db/seed.ts', // Database seeding script + 'src/db/seed_admin_account.ts', // Database seeding script + 'src/db/backup_user.ts', // Database backup script ], }, }, -}); \ No newline at end of file +}); diff --git a/vitest.config.integration.ts b/vitest.config.integration.ts index 2deab03..7c6bb3a 100644 --- a/vitest.config.integration.ts +++ b/vitest.config.integration.ts @@ -6,7 +6,7 @@ import viteConfig from './vite.config'; // Ensure NODE_ENV is set to 'test' for all Vitest runs. process.env.NODE_ENV = 'test'; -// 1. Separate the 'test' config (which has Unit Test settings) +// 1. Separate the 'test' config (which has Unit Test settings) // from the rest of the general Vite config (plugins, aliases, etc.) // DEBUG: Use console.error to ensure logs appear in CI/CD output console.error('[DEBUG] Loading vitest.config.integration.ts...'); @@ -30,38 +30,41 @@ console.error('[DEBUG] Base vite config keys:', Object.keys(baseViteConfig)); * It MERGES with the main vite.config.ts to inherit plugins and aliases, * then overrides the test-specific settings for a Node.js environment. */ -const finalConfig = mergeConfig(baseViteConfig, defineConfig({ - test: { - // Override settings from the main config for this specific test project. - name: 'integration', - environment: 'node', - // Point specifically to the new integration tests directory. - // This pattern will match any test file inside `src/tests/integration/`. - include: ['src/tests/integration/**/*.test.{ts,tsx}'], - // CRITICAL: We must override the `exclude` property from the base vite.config.ts. - // Otherwise, the inherited `exclude` rule will prevent any integration tests from running. - // Setting it to an empty array removes all exclusion rules for this project. - exclude: [], - // This setup script starts the backend server before tests run. - globalSetup: './src/tests/setup/integration-global-setup.ts', - // The default timeout is 5000ms (5 seconds) - testTimeout: 60000, // Increased timeout for server startup and API calls, especially AI services. - // "singleThread: true" is removed in modern Vitest. - // Use fileParallelism: false to ensure test files run one by one to prevent port conflicts. - fileParallelism: false, - coverage: { - provider: 'v8', - // We remove 'text' here. The final text report will be generated by `nyc` after merging. - reporter: ['html', 'json-summary', 'json'], - reportsDirectory: '.coverage/integration', - reportOnFailure: true, // This ensures the report generates even if tests fail - clean: true, +const finalConfig = mergeConfig( + baseViteConfig, + defineConfig({ + test: { + // Override settings from the main config for this specific test project. + name: 'integration', + environment: 'node', + // Point specifically to the new integration tests directory. + // This pattern will match any test file inside `src/tests/integration/`. + include: ['src/tests/integration/**/*.test.{ts,tsx}'], + // CRITICAL: We must override the `exclude` property from the base vite.config.ts. + // Otherwise, the inherited `exclude` rule will prevent any integration tests from running. + // Setting it to an empty array removes all exclusion rules for this project. + exclude: [], + // This setup script starts the backend server before tests run. + globalSetup: './src/tests/setup/integration-global-setup.ts', + // The default timeout is 5000ms (5 seconds) + testTimeout: 60000, // Increased timeout for server startup and API calls, especially AI services. + // "singleThread: true" is removed in modern Vitest. + // Use fileParallelism: false to ensure test files run one by one to prevent port conflicts. + fileParallelism: false, + coverage: { + provider: 'v8', + // We remove 'text' here. The final text report will be generated by `nyc` after merging. + reporter: ['html', 'json-summary', 'json'], + reportsDirectory: '.coverage/integration', + reportOnFailure: true, // This ensures the report generates even if tests fail + clean: true, + }, }, - } -})); + }), +); console.error('[DEBUG] Integration Final Config - INCLUDE:', finalConfig.test?.include); console.error('[DEBUG] Integration Final Config - EXCLUDE:', finalConfig.test?.exclude); console.error('[DEBUG] ----------------------------------\n'); -export default finalConfig; \ No newline at end of file +export default finalConfig; diff --git a/vitest.workspace.ts b/vitest.workspace.ts index 028a3fb..5ae1e1e 100644 --- a/vitest.workspace.ts +++ b/vitest.workspace.ts @@ -7,8 +7,11 @@ * - Integration tests are defined in `vitest.config.integration.ts` and run in a 'node' environment. */ export default [ - // DEBUGGING LOG - ((): string => { console.error('\n[DEBUG] Loading vitest.workspace.ts'); return ''; })(), - 'vite.config.ts', // Defines the 'unit' test project + // DEBUGGING LOG + ((): string => { + console.error('\n[DEBUG] Loading vitest.workspace.ts'); + return ''; + })(), + 'vite.config.ts', // Defines the 'unit' test project 'vitest.config.integration.ts', // Defines the 'integration' test project -]; \ No newline at end of file +];