diff --git a/Overview.json b/Overview.json index 67b355d..21a6886 100644 --- a/Overview.json +++ b/Overview.json @@ -1,7 +1,7 @@ { "title": "Bitip - Professional GeoIP Lookup Service", "subtitle": "Modern GeoIP lookup service with REST API and interactive web interface", - "lastUpdated": "2025-10-01T12:00:00Z", + "lastUpdated": "2025-10-05T14:00:00Z", "sections": [ { "title": "Overview", @@ -31,6 +31,7 @@ "**Node.js 18+** with ES Modules (ESM) for modern JavaScript features", "**Express 5.x** - Fast, minimalist web framework with enhanced routing capabilities", "**TypeScript 5.x** - Type-safe development with latest language features and compile-time checking", + "**tsup** - Modern bundler built on esbuild for 10x faster builds (~88ms) and cleaner imports without .js extensions", "**MaxMind GeoIP2 Node.js API** - Official MaxMind library for GeoLite2 database integration", "**express-rate-limit 8.x** - Advanced rate limiting with IP tracking and customizable limits", "**Helmet** - Security middleware that sets various HTTP headers to protect Express applications", diff --git a/README.md b/README.md index ee8caea..c64a6cc 100644 --- a/README.md +++ b/README.md @@ -320,18 +320,19 @@ npm run install:all # Install all dependencies (root + backend + frontend) **Backend:** -- Express.js 4.x with TypeScript -- @maxmind/geoip2-node 5.x - GeoIP database reader +- Express.js 5.x with TypeScript +- @maxmind/geoip2-node 6.x - GeoIP database reader - node-cache - In-memory caching -- express-rate-limit - Rate limiting +- express-rate-limit 8.x - Rate limiting - helmet - Security headers - joi - Request validation - seq-logging - Optional structured logging +- **tsup** - Modern bundler for fast builds (88ms vs ~1000ms with tsc) ⚡ **Frontend:** -- React 18.x with TypeScript -- Vite 4.x - Build tool and dev server +- React 19.x with TypeScript +- Vite 7.x - Build tool and dev server - Leaflet & react-leaflet - Interactive maps - Axios - HTTP client - OpenStreetMap - Map tiles @@ -341,7 +342,14 @@ npm run install:all # Install all dependencies (root + backend + frontend) - TypeScript 5.x - ESLint & Prettier - nodemon - Auto-restart on changes -- ts-node - TypeScript execution +- tsx - TypeScript execution + +**Build System:** + +- **tsup** (backend) - esbuild-based bundler with ESM support, no `.js` extensions needed +- **Vite** (frontend) - Lightning-fast HMR and optimized production builds + +> 📖 See [tsup Migration Guide](docs/tsup-migration.md) for details on the modern build system. ## ⚙️ Configuration diff --git a/ReleaseNotes.json b/ReleaseNotes.json index 48e802c..96cf00d 100644 --- a/ReleaseNotes.json +++ b/ReleaseNotes.json @@ -1,5 +1,62 @@ { "releases": [ + { + "version": "1.0.1", + "date": "2025-10-05T02:29:00Z", + "title": "Build System Modernization - tsup Migration", + "summary": "Migrated backend build system from tsc to tsup for cleaner imports, faster builds, and improved developer experience.", + "sections": [ + { + "title": "Overview", + "content": "Version 1.0.1 brings significant improvements to the backend build system by migrating from TypeScript Compiler (tsc) to tsup, a modern bundler built on esbuild. This change eliminates the need for .js extensions in imports and provides faster build times with optimized bundling." + }, + { + "title": "Build System Improvements", + "items": [ + "**Migrated to tsup** - Modern bundler for backend with esbuild performance", + "**Cleaner imports** - No more `.js` extensions required in TypeScript imports", + "**Faster builds** - ~10x faster build times (82ms vs ~1000ms with tsc)", + "**Single bundle output** - One optimized `index.js` file for production", + "**Better DX** - Imports match source code exactly, no mental overhead", + "**Sourcemaps included** - Full debugging support with accurate stack traces", + "**Tree-shaking enabled** - Smaller bundle size with unused code elimination" + ] + }, + { + "title": "Technical Changes", + "items": [ + "Changed `moduleResolution` from `node` to `bundler` in tsconfig.json", + "Removed all `.js` extensions from backend imports (handlers, middleware, routes, services)", + "Added `tsup.config.ts` with optimized ESM output configuration", + "Updated build script from `tsc` to `tsup` in package.json", + "Maintained development mode with nodemon for hot-reload support" + ] + }, + { + "title": "Benefits", + "items": [ + "**Improved Developer Experience** - Write imports without file extensions, matching frontend patterns", + "**Faster CI/CD Pipelines** - Reduced build time improves deployment speed", + "**Smaller Production Bundle** - Single optimized file reduces startup overhead", + "**Future-proof Tooling** - Modern bundler aligns with ecosystem trends (tRPC, Hono, Drizzle use tsup)", + "**Maintained Compatibility** - ESM output unchanged, Docker builds work identically" + ] + }, + { + "title": "Backward Compatibility", + "content": "This is a build-time change only. The runtime behavior, API contracts, and deployment process remain unchanged. Docker images continue to work with the same environment variables and configuration." + }, + { + "title": "Migration Notes", + "items": [ + "Frontend builds unchanged (Vite continues to handle bundling)", + "No changes required to environment variables or deployment scripts", + "Source code now has cleaner imports without `.js` extensions", + "Build output is a single `dist/backend/index.js` instead of multiple files" + ] + } + ] + }, { "version": "1.0.0", "date": "2025-10-01T12:00:00Z", diff --git a/package-lock.json b/package-lock.json index 77bab73..c8f452b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,6 @@ "src/backend", "src/frontend" ], - "dependencies": { - "dotenv": "^17.2.3" - }, "devDependencies": { "concurrently": "^9.2.1", "prettier": "^3.4.2", @@ -1206,9 +1203,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, @@ -1270,6 +1267,17 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/core": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", @@ -2046,6 +2054,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2195,6 +2210,22 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2204,6 +2235,16 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2451,6 +2492,16 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -2528,6 +2579,23 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -3354,6 +3422,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -3967,6 +4047,16 @@ "node": ">= 20" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4064,6 +4154,36 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4087,6 +4207,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", @@ -4097,6 +4224,16 @@ "node": "20 || >=22" } }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -4228,6 +4365,19 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, "node_modules/mmdb-lib": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-2.2.1.tgz", @@ -4244,6 +4394,18 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -4592,6 +4754,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4612,6 +4781,28 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -4641,6 +4832,49 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5279,6 +5513,20 @@ "node": ">=10" } }, + "node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5289,6 +5537,35 @@ "node": ">=0.10.0" } }, + "node_modules/source-map/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/source-map/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/source-map/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -5415,6 +5692,106 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/sucrase/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -5455,6 +5832,29 @@ "license": "MIT", "peer": true }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/tiny-lru": { "version": "11.3.3", "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.3.3.tgz", @@ -5464,6 +5864,13 @@ "node": ">=12" } }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -5560,6 +5967,13 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -5611,6 +6025,99 @@ "dev": true, "license": "0BSD" }, + "node_modules/tsup": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.0.tgz", + "integrity": "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.25.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "0.8.0-beta.0", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsup/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/tsup/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/tsup/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5678,6 +6185,13 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -6128,6 +6642,7 @@ "@maxmind/geoip2-node": "^6.1.0", "compression": "^1.7.4", "cors": "^2.8.5", + "dotenv": "^17.2.3", "express": "^5.1.0", "express-rate-limit": "^8.1.0", "helmet": "^8.1.0", @@ -6149,6 +6664,7 @@ "prettier": "^3.4.2", "rimraf": "^6.0.1", "ts-node": "^10.9.2", + "tsup": "^8.5.0", "typescript": "^5.9.2" } }, diff --git a/package.json b/package.json index 67f8827..2399d25 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitip", - "version": "1.0.0", + "version": "1.0.1", "description": "Bitip - GeoIP Lookup Service with REST API and Web Interface", "main": "dist/backend/index.js", "scripts": { diff --git a/src/backend/handlers/healthHandler.ts b/src/backend/handlers/healthHandler.ts index a308b4b..ada1fc5 100644 --- a/src/backend/handlers/healthHandler.ts +++ b/src/backend/handlers/healthHandler.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; -import geoIPService from '../services/geoip.js'; -import logger from '../services/logger.js'; +import geoIPService from '../services/geoip'; +import logger from '../services/logger'; export const healthCheckHandler = async ( _req: Request, diff --git a/src/backend/index.ts b/src/backend/index.ts index aa24466..24b1e39 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -6,13 +6,13 @@ import path from 'path'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import { setTimeout } from 'timers'; -import apiRoutes from './routes/api.js'; -import apiKeyAuth from './middleware/auth.js'; -import dynamicRateLimit from './middleware/rateLimit.js'; -import config from './services/config.js'; -import logger from './services/logger.js'; -import { generateRuntimeConfig } from './services/runtimeConfig.js'; -import { healthCheckHandler } from './handlers/healthHandler.js'; +import apiRoutes from './routes/api'; +import apiKeyAuth from './middleware/auth'; +import dynamicRateLimit from './middleware/rateLimit'; +import config from './services/config'; +import logger from './services/logger'; +import { generateRuntimeConfig } from './services/runtimeConfig'; +import { healthCheckHandler } from './handlers/healthHandler'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/src/backend/middleware/auth.ts b/src/backend/middleware/auth.ts index 5e03313..0275e19 100644 --- a/src/backend/middleware/auth.ts +++ b/src/backend/middleware/auth.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from 'express'; -import config from '../services/config.js'; -import logger from '../services/logger.js'; +import config from '../services/config'; +import logger from '../services/logger'; interface AuthenticatedRequest extends Request { apiKeyType?: 'frontend' | 'external'; diff --git a/src/backend/middleware/rateLimit.ts b/src/backend/middleware/rateLimit.ts index b99810c..b3ec8f9 100644 --- a/src/backend/middleware/rateLimit.ts +++ b/src/backend/middleware/rateLimit.ts @@ -1,7 +1,7 @@ import rateLimit from 'express-rate-limit'; import { Request, Response, NextFunction } from 'express'; -import config from '../services/config.js'; -import logger from '../services/logger.js'; +import config from '../services/config'; +import logger from '../services/logger'; interface AuthenticatedRequest extends Request { apiKeyType?: 'frontend' | 'external'; diff --git a/src/backend/package.json b/src/backend/package.json index e10a6dd..a5e938a 100644 --- a/src/backend/package.json +++ b/src/backend/package.json @@ -1,34 +1,34 @@ { "name": "bitip-backend", - "version": "1.0.0", + "version": "1.0.1", "description": "Bitip Backend - GeoIP REST API Service", "type": "module", "main": "dist/index.js", "scripts": { "dev": "nodemon", - "build": "rimraf ../../dist/backend && tsc", + "build": "tsup", "start": "node dist/index.js", "lint": "eslint .", "lint:fix": "eslint . --fix", - "clean": "rimraf dist" + "clean": "rimraf ../../dist/backend" }, "dependencies": { "@maxmind/geoip2-node": "^6.1.0", + "compression": "^1.7.4", + "cors": "^2.8.5", + "dotenv": "^17.2.3", "express": "^5.1.0", "express-rate-limit": "^8.1.0", "helmet": "^8.1.0", - "cors": "^2.8.5", - "compression": "^1.7.4", "joi": "^18.0.1", - "seq-logging": "^3.0.0", "node-cache": "^5.1.2", - "dotenv": "^17.2.3" + "seq-logging": "^3.0.0" }, "devDependencies": { + "@types/compression": "^1.7.5", + "@types/cors": "^2.8.17", "@types/express": "^5.0.3", "@types/node": "^22.14.1", - "@types/cors": "^2.8.17", - "@types/compression": "^1.7.5", "@typescript-eslint/eslint-plugin": "^8.45.0", "@typescript-eslint/parser": "^8.45.0", "eslint": "^9.36.0", @@ -38,6 +38,7 @@ "prettier": "^3.4.2", "rimraf": "^6.0.1", "ts-node": "^10.9.2", + "tsup": "^8.5.0", "typescript": "^5.9.2" } } diff --git a/src/backend/routes/api.ts b/src/backend/routes/api.ts index 29b119a..a9bdc6e 100644 --- a/src/backend/routes/api.ts +++ b/src/backend/routes/api.ts @@ -3,15 +3,15 @@ import Joi from 'joi'; import { readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; -import geoIPService from '../services/geoip.js'; -import logger from '../services/logger.js'; -import config from '../services/config.js'; -import { healthCheckHandler } from '../handlers/healthHandler.js'; +import geoIPService from '../services/geoip'; +import logger from '../services/logger'; +import config from '../services/config'; +import { healthCheckHandler } from '../handlers/healthHandler'; import { BatchGeoIPRequest, BatchGeoIPResponse, ErrorResponse, -} from '../types/index.js'; +} from '../types/index'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/src/backend/services/config.ts b/src/backend/services/config.ts index ac383f6..df8c4b3 100644 --- a/src/backend/services/config.ts +++ b/src/backend/services/config.ts @@ -1,4 +1,4 @@ -import { Config, LogLevel, LOG_LEVELS } from '../types/index.js'; +import { Config, LogLevel, LOG_LEVELS } from '../types/index'; import path from 'path'; import { config as dotenvConfig } from 'dotenv'; import { fileURLToPath } from 'url'; diff --git a/src/backend/services/geoip.ts b/src/backend/services/geoip.ts index e54aa80..81d1749 100644 --- a/src/backend/services/geoip.ts +++ b/src/backend/services/geoip.ts @@ -6,9 +6,9 @@ import { GeoIPLocation, SimplifiedGeoIPResponse, DetailedGeoIPResponse, -} from '../types/index.js'; -import config from './config.js'; -import logger from './logger.js'; +} from '../types/index'; +import config from './config'; +import logger from './logger'; class GeoIPService { private cityReader?: ReaderModel; diff --git a/src/backend/services/logger.ts b/src/backend/services/logger.ts index 42e4592..ddfd7b6 100644 --- a/src/backend/services/logger.ts +++ b/src/backend/services/logger.ts @@ -1,6 +1,6 @@ import { Logger as SeqLogger, SeqLoggerConfig } from 'seq-logging'; -import { LogLevel, LOG_LEVELS, LOG_LEVEL_PRIORITY } from '../types/index.js'; -import config from './config.js'; +import { LogLevel, LOG_LEVELS, LOG_LEVEL_PRIORITY } from '../types/index'; +import config from './config'; class Logger { private seqLogger?: SeqLogger; diff --git a/src/backend/services/runtimeConfig.ts b/src/backend/services/runtimeConfig.ts index 1b9176d..61e17bf 100644 --- a/src/backend/services/runtimeConfig.ts +++ b/src/backend/services/runtimeConfig.ts @@ -1,7 +1,7 @@ import { writeFileSync, existsSync } from 'fs'; import { join } from 'path'; -import logger from './logger.js'; -import config from './config.js'; +import logger from './logger'; +import config from './config'; /** * Generates the runtime configuration file (env.js) for the frontend diff --git a/src/backend/tsconfig.json b/src/backend/tsconfig.json index 3feb476..ce075d6 100644 --- a/src/backend/tsconfig.json +++ b/src/backend/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "ES2022", "module": "ES2022", - "moduleResolution": "node", + "moduleResolution": "bundler", "lib": ["ES2022"], "outDir": "../../dist/backend", "rootDir": ".", diff --git a/src/backend/tsup.config.ts b/src/backend/tsup.config.ts new file mode 100644 index 0000000..725085d --- /dev/null +++ b/src/backend/tsup.config.ts @@ -0,0 +1,25 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['index.ts'], + format: ['esm'], + outDir: '../../dist/backend', + target: 'node22', + platform: 'node', + splitting: false, + sourcemap: true, // Keep sourcemaps for debugging production issues + clean: true, + minify: true, // Minify for production (smaller bundle, faster startup) + bundle: true, + shims: false, // No need for __dirname/__filename shims in ESM + dts: false, // No need for .d.ts files in production backend + external: [ + // Exclude all dependencies from bundle (use node_modules) + /^[^./]|^\.[^./]|^\.\.[^/]/, + ], + noExternal: [], + treeshake: true, + env: { + NODE_ENV: 'production', + }, +}); diff --git a/src/backend/types/index.ts b/src/backend/types/index.ts index 5bc5c22..d88d384 100644 --- a/src/backend/types/index.ts +++ b/src/backend/types/index.ts @@ -1,6 +1,6 @@ -import { LogLevel } from './log.js'; +import { LogLevel } from './log'; -export * from './log.js'; +export * from './log'; export interface GeoIPLocation { country?: { diff --git a/src/frontend/index.html b/src/frontend/index.html index 39905f9..89e1c3f 100644 --- a/src/frontend/index.html +++ b/src/frontend/index.html @@ -2,7 +2,7 @@ - + Bitip - GeoIP Lookup Service - + diff --git a/src/frontend/src/utils/paths.ts b/src/frontend/src/utils/paths.ts index 6362eb7..5a98a69 100644 --- a/src/frontend/src/utils/paths.ts +++ b/src/frontend/src/utils/paths.ts @@ -8,15 +8,20 @@ * pathCombine('/', '/api') // '/api' */ const pathCombine = (...segments: string[]): string => { - if (segments.length === 0) return ''; - if (segments.length === 1) return segments[0]; + // Filter out null, undefined, and empty strings + const validSegments = segments.filter( + seg => seg !== null && seg !== undefined && seg !== '' + ); + + if (validSegments.length === 0) return ''; + if (validSegments.length === 1) return validSegments[0]; // Process first segment (keep leading slash or protocol) - let result = segments[0].replace(/\/+$/, ''); + let result = validSegments[0].replace(/\/+$/, ''); // Process middle and last segments - for (let i = 1; i < segments.length; i++) { - const segment = segments[i].replace(/^\/+|\/+$/g, ''); // Remove leading and trailing slashes + for (let i = 1; i < validSegments.length; i++) { + const segment = validSegments[i].replace(/^\/+|\/+$/g, ''); // Remove leading and trailing slashes if (segment) { // Only add non-empty segments result += `/${segment}`; diff --git a/src/frontend/vite.config.ts b/src/frontend/vite.config.ts index f66df92..bdd5cc0 100644 --- a/src/frontend/vite.config.ts +++ b/src/frontend/vite.config.ts @@ -6,7 +6,9 @@ import path from 'path'; export default defineConfig(({ mode }) => { // Load env file based on `mode` in the current working directory. const env = loadEnv(mode, process.cwd(), ''); - const basePath = env.VITE_BASE_PATH || '/'; + + // Normalize base path: ensure it ends with / for proper Vite %BASE_URL% replacement + const basePath = ensureTrailingSlash(env.VITE_BASE_PATH || '/'); return { base: basePath, @@ -30,3 +32,11 @@ export default defineConfig(({ mode }) => { }, }; }); + +const ensureTrailingSlash = (url: string) => { + if (url === null || url === undefined) return url; + if (url.endsWith('/')) { + return url; + } + return `${url}/`; +};