CLAUDE.md
Rama main · réplica de lectura del repositorio original.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
QuickYNAB is a TypeScript-based tool for importing bank transactions to YNAB with two interfaces:
- **Web App**: Fastify server with Vite frontend (drag-and-drop CSV upload)
- **CLI**: Commander.js-based command-line tool for automation
The project auto-detects 124+ bank CSV formats using configs fetched from [bank2ynab](https://github.com/bank2ynab/bank2ynab).
## Essential Commands
```bash
# Development
bun run dev # Run both frontend (Vite) and backend (bun --watch) concurrently
bun run dev:vite # Frontend only (http://localhost:5173)
bun run dev:server # Backend only (http://localhost:3000)
# Building
bun run build # Full build: fetch configs → build frontend → build backend → copy configs
bun run build:frontend # Vite build only (outputs to dist/public/)
bun run build:backend # TypeScript compile with bun x tsc (outputs to dist/)
# Testing
bun test # Run all tests with Bun's test runner
bun test --watch # Watch mode
bun test --coverage # Coverage report
bun run typecheck # Type check sources + tests (used in CI)
# Other
bun run format # Format with Biome
bun run format:check # Check formatting (used in CI)
bun run start # Run CLI (bun dist/cli.js)
bun run web # Run web server (bun dist/server.js)
```
## Architecture
### Build Process Flow
1. **`bun run build`** triggers 4 sequential steps:
- `fetch:configs` - Downloads bank2ynab.conf from GitHub using Bun, parses INI format, saves as `lib/parsers/bank2ynab-configs.json`
- `build:frontend` - Vite bundles React/TS frontend to `dist/public/`
- `build:backend` - TypeScript compiles backend to `dist/` using `bun x tsc`
- `copy:configs` - Copies JSON config to `dist/lib/parsers/`
**Critical:** Bank configs are fetched at **build time**, not runtime. This keeps the bundle small and startup fast.
**Runtime:** The project runs on Bun, which provides faster startup times and native TypeScript support compared to Node.js.
### CSV Parsing Pipeline
The parsing flow differs for CLI vs Web App but uses the same core logic:
```
parseCSV(filePath, originalFilename?)
↓
getBank2YnabConfigs() - loads bundled configs from JSON
↓
findMatchingConfig(filename) - regex or string matching against patterns
↓
[IF MATCH] → parseBank2YnabCSV() - handles semicolons, headers, footers, date formats
[IF NO MATCH] → parseYnabCSV() - standard YNAB format (Date, Payee, Outflow, Inflow)
↓
Returns Transaction[] with normalized format
```
**Web App caveat:** The server saves uploads as temp files with random prefixes (`ynab-{hash}-{originalFilename}.csv`). The `originalFilename` parameter is essential for pattern matching since bank configs use `^` anchors that expect specific filename patterns (e.g., `^(\d{4})_(\d{1,2})_account_statements` for CH Neon).
### Transaction Upload Flow
```
uploadTransactions(transactions, config, accountIdOverride?, budgetIdOverride?)
↓
Resolve budget ID (override > config > auto-select if only 1 > error)
↓
Resolve account ID (override > config > auto-select if only 1 > error)
↓
Generate import_id for each transaction (MD5 hash of account:date:amount:payee)
↓
Convert amounts to milliunits (multiply by 1000, round)
↓
ynabAPI.transactions.createTransactions()
↓
Returns { imported, duplicates } - YNAB skips transactions with duplicate import_ids
```
### File Structure
```
cli.ts # CLI entry point (Commander.js)
server.ts # Web server (Fastify + multipart uploads)
src/ # Frontend (Vite + TypeScript + Tailwind)
├── main.ts # Frontend logic (drag-drop, preview, upload)
└── index.html # Entry HTML
lib/
├── config.ts # Loads from ~/.quickynab/config or .env
├── converter.ts # Main CSV parsing entry point
├── uploader.ts # YNAB API interaction
└── parsers/
├── bank2ynab-fetcher.ts # Config loading & pattern matching
├── bank2ynab-generic.ts # Generic bank CSV parser
└── bank2ynab-configs.json # Generated at build time
scripts/
└── fetch-bank-configs.ts # Downloads & parses bank2ynab.conf
```
## Key Implementation Details
### Bank Format Detection
Pattern matching happens in `findMatchingConfig()`:
1. Iterates through all configs in order (order matters - more specific patterns should come first)
2. For each config, checks `useRegex` flag:
- `true`: Tests filename against regex pattern
- `false`: Checks if filename includes pattern string
3. Returns first match or null
**When adding/fixing banks:** Check the pattern in `bank2ynab-configs.json`. If a pattern is too generic (e.g., `[0-9]{10}`), it may match before more specific patterns.
### Configuration Loading
Two separate config systems:
- **CLI**: Reads `~/.quickynab/config` (created by `ynab init` command)
- **Web**: Reads `.env` file or environment variables
- Both use `lib/config.ts` which exports `getConfig()`
**Budget/Account preselection:**
- **CLI**: Automatically uses `YNAB_BUDGET_ID` and `YNAB_ACCOUNT_ID` from `~/.quickynab/config` (created by `ynab init`). Can be overridden with `--budget-id` and `--account-id` flags.
- **Web App**: If `YNAB_BUDGET_ID` and/or `YNAB_ACCOUNT_ID` are set in `.env`, the web app will automatically preselect them in the dropdowns on page load. Users can still change the selections if desired.
### Frontend State Management
No React/Vue framework - vanilla TypeScript with DOM manipulation:
- Global state vars (`currentFile`, `previewData`, `selectedBudgetId`, `selectedAccountId`)
- Event listeners on drop-zone, file input, budget select, account select
- Dynamic HTML generation for preview and results
**Dark mode:** Uses Tailwind's `dark:` classes with `media` strategy (system preference)
### Security (Web App)
- Helmet for security headers (CSP, XSS protection)
- Rate limiting (100 req/15min global, 10 req/1min for uploads)
- File validation (max 10MB, CSV only, malicious content detection)
- Temp files with crypto-random names, cleaned up in finally blocks
- **Alpine-based Docker image** - Minimal base image with only required dependencies
- Built-in health checks for container orchestration
- **Zod validation** for runtime type safety on API inputs
## Testing Strategy
Tests use Bun's built-in test runner (imported from `bun:test`) with 71 tests across 7 files:
- `bank2ynab-fetcher.test.ts` - Pattern matching (regex vs string, edge cases)
- `bank2ynab-generic.test.ts` - CSV parsing (delimiters, skip rows, sanitization)
- `date-parser.test.ts` - Date format detection (DD.MM.YYYY, YYYY-MM-DD, etc.)
- `uploader.test.ts` - Milliunits conversion, import_id generation
- `converter.test.ts` - Full CSV parsing flow
- `config.test.ts` - Configuration loading and validation
- `server.test.ts` - Web server endpoints and security
**When modifying parsers:** Run `bun test --watch` and ensure existing tests pass. Add tests for new bank formats.
**Performance:** Bun's test runner is significantly faster than Vitest (~340ms vs ~919ms for the full suite).
## Publishing
### Release process
1. Bump `version` in `package.json`
2. Commit, tag and push:
```bash
git add package.json
git commit -m "chore: bump version to x.y.z"
git tag vx.y.z
git push && git push --tags
```
3. Publish to npm manually:
```bash
npm publish
```
(runs `prepublishOnly` → `check && typecheck && test && build` automatically)
### What's automated
- **Docker:** `.github/workflows/docker-publish.yml` triggers on `v*` tag push
- Builds multi-platform (linux/amd64, linux/arm64)
- Pushes to both Docker Hub and GHCR
- Uses official Bun Alpine image
### What's manual
- **npm:** No CI workflow — run `npm publish` locally after tagging
## Common Gotchas
1. **Bank configs not updating?** Run `bun run fetch:configs` to re-download from GitHub
2. **Web app not detecting bank?** Check that original filename is passed to `parseCSV()` - temp file prefix breaks pattern matching
3. **Date parsing fails?** The `date-parser.ts` tries multiple formats - add new format if needed
4. **Duplicate transactions?** import_id must be consistent - check hash generation in `uploader.ts`
5. **Frontend changes not showing?** Run `bun run build:frontend` (dev server runs on :5173, production on :3000)
6. **Bun compatibility issues?** Bun has excellent Node.js compatibility, but check https://bun.sh/docs/runtime/nodejs-apis for any edge cases
7. **Fastify/zod in `optionalDependencies`?** Deliberate, don't "fix" it. Only `server.ts` needs them, so `npm i -g quickynab --omit=optional` gives a working CLI at 5.7 MB instead of 34 MB. The trade-off: a partial install fails at `bun run web` with a plain `Cannot find package 'fastify'`, because static ESM imports cannot be guarded without splitting the server entry point.