From 315303b120a4299d4716c4fee190c93820a5c07c Mon Sep 17 00:00:00 2001 From: William Valentin Date: Sun, 7 Sep 2025 13:34:39 -0700 Subject: [PATCH] Fix pre-commit script to properly handle multiple files and resolve ESLint warnings --- .gitea/README.md | 25 ++-- .gitea/gitea-bake.hcl | 26 ++-- App.tsx | 11 +- SECURITY.md | 21 +-- bun.lock | 120 +++++++++++++++++- components/auth/AuthPage.tsx | 2 +- components/auth/ChangePasswordModal.tsx | 6 +- components/modals/AccountModal.tsx | 5 +- contexts/UserContext.tsx | 26 ++-- docker/Dockerfile | 7 +- docker/docker-bake.hcl | 16 +-- docs/APP_NAME_CONFIGURATION.md | 28 ++-- eslint.config.cjs | 11 +- hooks/useLocalStorage.ts | 2 +- index.html | 2 +- package.json | 4 +- rename-app.sh | 78 ++++++------ scripts/gitea-deploy.sh | 108 ++++++++-------- scripts/gitea-helper.sh | 112 ++++++++-------- scripts/k8s-deploy-template.sh | 68 +++++----- scripts/pre-commit-checks.sh | 12 +- scripts/seed-production.js | 28 ++-- scripts/undeploy-k8s.sh | 62 ++++----- scripts/validate-env.sh | 68 +++++----- .../auth/__tests__/auth.integration.test.ts | 1 - .../auth/__tests__/emailVerification.test.ts | 1 - services/auth/auth.middleware.ts | 2 +- services/auth/auth.service.ts | 26 ++-- services/couchdb.factory.ts | 4 +- services/couchdb.production.ts | 34 ++--- services/mailgun.service.ts | 26 ++-- services/oauth.ts | 9 +- vite-env.d.ts | 14 ++ 33 files changed, 561 insertions(+), 404 deletions(-) create mode 100644 vite-env.d.ts diff --git a/.gitea/README.md b/.gitea/README.md index 021bb06..3e718da 100644 --- a/.gitea/README.md +++ b/.gitea/README.md @@ -159,23 +159,24 @@ Configure `DEPLOYMENT_WEBHOOK_URL` to receive notifications: 1. **Build Fails - Buildx Not Available** - ```bash - # Ensure Docker Buildx is installed on runner - docker buildx version - ``` +```bash +# Ensure Docker Buildx is installed on runner +docker buildx version +``` 2. **Registry Push Fails** - ```bash - # Check GITEA_TOKEN has package write permissions - # Verify registry URL is correct - ``` +```bash +# Check GITEA_TOKEN has package write permissions +# Verify registry URL is correct +``` 3. **Deployment Fails** - ```bash - # Check environment variables are set - # Verify server has Docker/Kubernetes access - ``` + +```bash +# Check environment variables are set +# Verify server has Docker/Kubernetes access +``` ### Debug Commands diff --git a/.gitea/gitea-bake.hcl b/.gitea/gitea-bake.hcl index d2a7e70..2e4074a 100644 --- a/.gitea/gitea-bake.hcl +++ b/.gitea/gitea-bake.hcl @@ -56,12 +56,12 @@ target "app" { "linux/amd64", "linux/arm64" ] - + tags = [ "${GITEA_REGISTRY}/${GITEA_REPOSITORY}:${TAG}", "${GITEA_REGISTRY}/${GITEA_REPOSITORY}:latest" ] - + args = { VITE_COUCHDB_URL = "${VITE_COUCHDB_URL}" VITE_COUCHDB_USER = "${VITE_COUCHDB_USER}" @@ -71,12 +71,12 @@ target "app" { VITE_GITHUB_CLIENT_ID = "${VITE_GITHUB_CLIENT_ID}" NODE_ENV = "production" } - + # Gitea registry caching cache-from = [ "type=registry,ref=${GITEA_REGISTRY}/${GITEA_REPOSITORY}:buildcache" ] - + cache-to = [ "type=registry,ref=${GITEA_REGISTRY}/${GITEA_REPOSITORY}:buildcache,mode=max" ] @@ -89,13 +89,13 @@ target "app-ci" { "${GITEA_REGISTRY}/${GITEA_REPOSITORY}:${GITEA_SHA}", "${GITEA_REGISTRY}/${GITEA_REPOSITORY}:latest" ] - + # Enhanced CI-specific features attest = [ "type=provenance,mode=max", "type=sbom" ] - + # CI registry push output = ["type=registry"] } @@ -105,11 +105,11 @@ target "dev" { inherits = ["app"] platforms = ["linux/amd64"] tags = ["rxminder:dev"] - + # Local caching only cache-from = ["type=registry,ref=${GITEA_REGISTRY}/${GITEA_REPOSITORY}:buildcache"] cache-to = ["type=registry,ref=${GITEA_REGISTRY}/${GITEA_REPOSITORY}:buildcache"] - + # Load locally instead of push output = ["type=docker"] } @@ -117,13 +117,13 @@ target "dev" { # Production target with full attestations target "prod" { inherits = ["app-ci"] - + # Production-specific tags tags = [ "${GITEA_REGISTRY}/${GITEA_REPOSITORY}:prod-${TAG}", "${GITEA_REGISTRY}/${GITEA_REPOSITORY}:production" ] - + # Full security attestations for production attest = [ "type=provenance,mode=max", @@ -135,12 +135,12 @@ target "prod" { target "staging" { inherits = ["app"] platforms = ["linux/amd64"] # Single platform for staging - + tags = [ "${GITEA_REGISTRY}/${GITEA_REPOSITORY}:staging-${TAG}", "${GITEA_REGISTRY}/${GITEA_REPOSITORY}:staging" ] - + # Staging-specific build args args = { VITE_COUCHDB_URL = "${VITE_COUCHDB_URL}" @@ -151,6 +151,6 @@ target "staging" { VITE_GITHUB_CLIENT_ID = "${VITE_GITHUB_CLIENT_ID}" NODE_ENV = "staging" } - + output = ["type=registry"] } diff --git a/App.tsx b/App.tsx index cbd7c6a..bbf689b 100644 --- a/App.tsx +++ b/App.tsx @@ -18,7 +18,6 @@ import { ScheduleItem, DailyStat, MedicationStat, - UserRole, } from './types'; // Component imports - organized by feature @@ -43,7 +42,7 @@ import { OnboardingModal, StatsModal, } from './components/modals'; -import { BarChart, ReminderCard, ThemeSwitcher } from './components/ui'; +import { ReminderCard, ThemeSwitcher } from './components/ui'; // Icon and utility imports import { @@ -239,7 +238,7 @@ const MedicationScheduleApp: React.FC<{ user: User }> = ({ user }) => { setIsLoading(true); setError(null); - console.log('Fetching data for user:', user._id); + console.warn('Fetching data for user:', user._id); const [medsData, remindersData, takenDosesData, settingsData] = await Promise.all([ @@ -249,7 +248,7 @@ const MedicationScheduleApp: React.FC<{ user: User }> = ({ user }) => { dbService.getSettings(user._id), ]); - console.log('Data fetched successfully:', { + console.warn('Data fetched successfully:', { medications: medsData.length, reminders: remindersData.length, hasTakenDoses: !!takenDosesData, @@ -279,7 +278,7 @@ const MedicationScheduleApp: React.FC<{ user: User }> = ({ user }) => { }, 100); return () => clearTimeout(timeoutId); - }, [user._id]); + }, [user._id, user]); useEffect(() => { if ( @@ -911,7 +910,7 @@ const App: React.FC = () => { useEffect(() => { const runSeeding = async () => { try { - console.log('🌱 Initializing database seeding...'); + console.warn('🌱 Initializing database seeding...'); await databaseSeeder.seedDatabase(); } catch (error) { console.error('❌ Database seeding failed:', error); diff --git a/SECURITY.md b/SECURITY.md index b5be695..2bf27b4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -62,21 +62,22 @@ INGRESS_HOST=rxminder.yourdomain.com 1. **Copy environment template:** - ```bash - cp .env.example .env - ``` +```bash +cp .env.example .env +``` 2. **Update .env with your secure credentials:** - ```bash - # Edit .env with your secure passwords and configuration - nano .env - ``` +```bash +# Edit .env with your secure passwords and configuration +nano .env +``` 3. **Deploy with templates:** - ```bash - ./scripts/k8s-deploy-template.sh deploy - ``` + +```bash +./scripts/k8s-deploy-template.sh deploy +``` The deployment script automatically: diff --git a/bun.lock b/bun.lock index 57b7b64..fc5b13d 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,8 @@ "": { "dependencies": { "bcryptjs": "^3.0.2", + "express": "^5.1.0", + "jsonwebtoken": "^9.0.2", "react": "^19.1.1", "react-dom": "^19.1.1", "uuid": "^12.0.0", @@ -472,6 +474,8 @@ "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -548,6 +552,8 @@ "binaryextensions": ["binaryextensions@6.11.0", "", { "dependencies": { "editions": "^6.21.0" } }, "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw=="], + "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], + "boundary": ["boundary@2.0.0", "", {}, "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA=="], "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], @@ -562,6 +568,8 @@ "buffer-equal": ["buffer-equal@1.0.1", "", {}, "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg=="], + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + "buffer-equals": ["buffer-equals@1.0.4", "", {}, "sha512-99MsCq0j5+RhubVEtKQgKaD6EM+UP3xJgIvQqwJ3SOLDUekzxMX1ylXBng+Wa2sh7mGT0W6RUly8ojjr1Tt6nA=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], @@ -570,6 +578,8 @@ "bufferstreams": ["bufferstreams@2.0.1", "", { "dependencies": { "readable-stream": "^2.3.6" } }, "sha512-ZswyIoBfFb3cVDsnZLLj2IDJ/0ppYdil/v2EGlZXvoefO689FokEmFEldhN5dV7R2QBxFneqTJOMIpfqhj+n0g=="], + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -630,8 +640,16 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="], + + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -664,6 +682,8 @@ "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="], @@ -680,12 +700,16 @@ "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + "eclint": ["eclint@2.8.1", "", { "dependencies": { "editorconfig": "^0.15.2", "file-type": "^10.1.0", "gulp-exclude-gitignore": "^1.2.0", "gulp-filter": "^5.1.0", "gulp-reporter": "^2.9.0", "gulp-tap": "^1.0.1", "linez": "^4.1.4", "lodash": "^4.17.11", "minimatch": "^3.0.4", "os-locale": "^3.0.1", "plugin-error": "^1.0.1", "through2": "^2.0.3", "vinyl": "^2.2.0", "vinyl-fs": "^3.0.3", "yargs": "^12.0.2" }, "bin": { "eclint": "bin/eclint.js" } }, "sha512-0u1UubFXSOgZgXNhuPeliYyTFmjWStVph8JR6uD6NDuxl3xI5VSCsA1KX6/BSYtM9v4wQMifGoNFfN5VlRn4LQ=="], "editions": ["editions@6.22.0", "", { "dependencies": { "version-range": "^4.15.0" } }, "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ=="], "editorconfig": ["editorconfig@0.15.3", "", { "dependencies": { "commander": "^2.19.0", "lru-cache": "^4.1.5", "semver": "^5.6.0", "sigmund": "^1.0.1" }, "bin": { "editorconfig": "bin/editorconfig" } }, "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g=="], + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "electron-to-chromium": ["electron-to-chromium@1.5.214", "", {}, "sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q=="], "emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="], @@ -694,6 +718,8 @@ "emphasize": ["emphasize@2.1.0", "", { "dependencies": { "chalk": "^2.4.0", "highlight.js": "~9.12.0", "lowlight": "~1.9.0" } }, "sha512-wRlO0Qulw2jieQynsS3STzTabIhHCyjTjZraSkchOiT8rdvWZlahJAJ69HRxwGkv2NThmci2MSnDfJ60jB39tw=="], + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -714,6 +740,8 @@ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "eslint": ["eslint@9.35.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.35.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg=="], @@ -738,6 +766,8 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], @@ -746,6 +776,8 @@ "expect": ["expect@30.1.2", "", { "dependencies": { "@jest/expect-utils": "30.1.2", "@jest/get-type": "30.1.0", "jest-matcher-utils": "30.1.2", "jest-message-util": "30.1.0", "jest-mock": "30.0.5", "jest-util": "30.0.5" } }, "sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg=="], + "express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="], + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], "extend-shallow": ["extend-shallow@3.0.2", "", { "dependencies": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" } }, "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q=="], @@ -776,6 +808,8 @@ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + "finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="], + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], @@ -792,6 +826,10 @@ "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + "fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="], "fs-mkdirp-stream": ["fs-mkdirp-stream@1.0.0", "", { "dependencies": { "graceful-fs": "^4.1.11", "through2": "^2.0.3" } }, "sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ=="], @@ -866,6 +904,8 @@ "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], @@ -896,6 +936,8 @@ "invert-kv": ["invert-kv@2.0.0", "", {}, "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA=="], + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + "is-absolute": ["is-absolute@1.0.0", "", { "dependencies": { "is-relative": "^1.0.0", "is-windows": "^1.0.1" } }, "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA=="], "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], @@ -944,6 +986,8 @@ "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], + "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], "is-relative": ["is-relative@1.0.0", "", { "dependencies": { "is-unc-path": "^1.0.0" } }, "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA=="], @@ -1066,8 +1110,14 @@ "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], + "jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="], + "junit-report-builder": ["junit-report-builder@1.3.3", "", { "dependencies": { "date-format": "0.0.2", "lodash": "^4.17.15", "mkdirp": "^0.5.0", "xmlbuilder": "^10.0.0" } }, "sha512-75bwaXjP/3ogyzOSkkcshXGG7z74edkJjgTZlJGAyzxlOHaguexM3VLG6JyD9ZBF8mlpgsUPB1sIWU4LISgeJw=="], + "jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="], + + "jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="], + "katex": ["katex@0.16.22", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg=="], "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], @@ -1102,10 +1152,24 @@ "lodash.get": ["lodash.get@4.4.2", "", {}, "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="], + "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], + + "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], + + "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="], + + "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="], + "lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="], "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + "lodash.truncate": ["lodash.truncate@4.4.2", "", {}, "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw=="], "log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="], @@ -1136,8 +1200,12 @@ "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="], + "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + "mem": ["mem@4.3.0", "", { "dependencies": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", "p-is-promise": "^2.0.0" } }, "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w=="], + "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], @@ -1194,6 +1262,10 @@ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], @@ -1220,6 +1292,8 @@ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], "nice-try": ["nice-try@1.0.5", "", {}, "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="], @@ -1246,6 +1320,8 @@ "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], @@ -1282,6 +1358,8 @@ "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + "path-dirname": ["path-dirname@1.0.2", "", {}, "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q=="], "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], @@ -1292,6 +1370,8 @@ "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], + "path-type": ["path-type@6.0.0", "", {}, "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], @@ -1324,6 +1404,8 @@ "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "pseudomap": ["pseudomap@1.0.2", "", {}, "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ=="], "pump": ["pump@2.0.1", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA=="], @@ -1336,8 +1418,14 @@ "pure-rand": ["pure-rand@7.0.1", "", {}, "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ=="], + "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "raw-body": ["raw-body@3.0.1", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.7.0", "unpipe": "1.0.0" } }, "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA=="], + "rc-config-loader": ["rc-config-loader@4.1.3", "", { "dependencies": { "debug": "^4.3.4", "js-yaml": "^4.1.0", "json5": "^2.2.2", "require-from-string": "^2.0.2" } }, "sha512-kD7FqML7l800i6pS6pvLyIE2ncbk9Du8Q0gp/4hMPhJU6ZxApkoLcGD8ZeqgiAlfwZ6BlETq6qqe+12DUL207w=="], "react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="], @@ -1380,11 +1468,13 @@ "rollup": ["rollup@4.50.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.50.0", "@rollup/rollup-android-arm64": "4.50.0", "@rollup/rollup-darwin-arm64": "4.50.0", "@rollup/rollup-darwin-x64": "4.50.0", "@rollup/rollup-freebsd-arm64": "4.50.0", "@rollup/rollup-freebsd-x64": "4.50.0", "@rollup/rollup-linux-arm-gnueabihf": "4.50.0", "@rollup/rollup-linux-arm-musleabihf": "4.50.0", "@rollup/rollup-linux-arm64-gnu": "4.50.0", "@rollup/rollup-linux-arm64-musl": "4.50.0", "@rollup/rollup-linux-loongarch64-gnu": "4.50.0", "@rollup/rollup-linux-ppc64-gnu": "4.50.0", "@rollup/rollup-linux-riscv64-gnu": "4.50.0", "@rollup/rollup-linux-riscv64-musl": "4.50.0", "@rollup/rollup-linux-s390x-gnu": "4.50.0", "@rollup/rollup-linux-x64-gnu": "4.50.0", "@rollup/rollup-linux-x64-musl": "4.50.0", "@rollup/rollup-openharmony-arm64": "4.50.0", "@rollup/rollup-win32-arm64-msvc": "4.50.0", "@rollup/rollup-win32-ia32-msvc": "4.50.0", "@rollup/rollup-win32-x64-msvc": "4.50.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw=="], + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + "rrweb-cssom": ["rrweb-cssom@0.8.0", "", {}, "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], @@ -1396,12 +1486,18 @@ "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + "send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="], + + "serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="], + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], @@ -1434,6 +1530,8 @@ "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], "stream-shift": ["stream-shift@1.0.3", "", {}, "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="], @@ -1508,6 +1606,8 @@ "to-time": ["to-time@1.0.2", "", { "dependencies": { "bignumber.js": "^2.4.0" } }, "sha512-+wqaiQvnido2DI1bpiQ/Zv1LiOE9Fd0v35ySnNeqFmKNYJTJY/+ENI+3sHXCMzbAAOR/43aNyLM0XTpi0/zSQg=="], + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + "tough-cookie": ["tough-cookie@5.1.2", "", { "dependencies": { "tldts": "^6.1.32" } }, "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A=="], "tr46": ["tr46@5.1.1", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="], @@ -1524,6 +1624,8 @@ "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], + "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], @@ -1540,6 +1642,8 @@ "universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + "unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="], "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], @@ -1554,6 +1658,8 @@ "value-or-function": ["value-or-function@3.0.0", "", {}, "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg=="], + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + "version-range": ["version-range@4.15.0", "", {}, "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg=="], "vinyl": ["vinyl@2.2.1", "", { "dependencies": { "clone": "^2.1.1", "clone-buffer": "^1.0.0", "clone-stats": "^1.0.0", "cloneable-readable": "^1.0.0", "remove-trailing-separator": "^1.0.1", "replace-ext": "^1.0.0" } }, "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw=="], @@ -1712,6 +1818,8 @@ "babel-plugin-istanbul/istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="], + "body-parser/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "buffered-spawn/cross-spawn": ["cross-spawn@4.0.2", "", { "dependencies": { "lru-cache": "^4.0.1", "which": "^1.2.9" } }, "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA=="], "chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], @@ -1754,6 +1862,8 @@ "gulp-reporter/through2": ["through2@3.0.2", "", { "dependencies": { "inherits": "^2.0.4", "readable-stream": "2 || 3" } }, "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ=="], + "http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], + "in-gfw/mem": ["mem@3.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0", "p-is-promise": "^1.1.0" } }, "sha512-QKs47bslvOE0NbXOqG6lMxn6Bk0Iuw0vfrIeLykmQle2LkCw1p48dZDdzE+D88b/xqRJcZGcMNeDvSVma+NuIQ=="], "istanbul-lib-report/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -1872,10 +1982,16 @@ "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + "raw-body/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], + "rc-config-loader/js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + "readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + "remove-bom-buffer/is-buffer": ["is-buffer@1.1.6", "", {}, "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="], + "remove-bom-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + "resolve-cwd/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], @@ -1898,6 +2014,8 @@ "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "string_decoder/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "supports-hyperlinks/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], diff --git a/components/auth/AuthPage.tsx b/components/auth/AuthPage.tsx index 9d6fa22..85eb554 100644 --- a/components/auth/AuthPage.tsx +++ b/components/auth/AuthPage.tsx @@ -97,7 +97,7 @@ const AuthPage: React.FC = () => { if (!success) { setError(`${provider} authentication failed. Please try again.`); } - } catch (error) { + } catch { setError(`${provider} authentication failed. Please try again.`); } }; diff --git a/components/auth/ChangePasswordModal.tsx b/components/auth/ChangePasswordModal.tsx index 68adf32..0d06d82 100644 --- a/components/auth/ChangePasswordModal.tsx +++ b/components/auth/ChangePasswordModal.tsx @@ -52,8 +52,10 @@ const ChangePasswordModal: React.FC = ({ await authService.changePassword(user!._id, currentPassword, newPassword); onSuccess(); onClose(); - } catch (error: any) { - setError(error.message || 'Failed to change password'); + } catch (error: unknown) { + setError( + error instanceof Error ? error.message : 'Failed to change password' + ); } finally { setLoading(false); } diff --git a/components/modals/AccountModal.tsx b/components/modals/AccountModal.tsx index 407f554..89d0c6b 100644 --- a/components/modals/AccountModal.tsx +++ b/components/modals/AccountModal.tsx @@ -46,7 +46,7 @@ const AccountModal: React.FC = ({ await onUpdateUser({ ...user, username: username.trim() }); setSuccessMessage('Username updated successfully!'); setTimeout(() => setSuccessMessage(''), 3000); - } catch (error) { + } catch { alert('Failed to update username.'); } finally { setIsSaving(false); @@ -77,6 +77,7 @@ const AccountModal: React.FC = ({ }; const handleRemoveAvatar = async () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { avatar, ...userWithoutAvatar } = user; setIsSaving(true); try { @@ -90,7 +91,7 @@ const AccountModal: React.FC = ({ setIsDeleting(true); try { await onDeleteAllData(); - } catch (error) { + } catch { alert('Failed to delete data.'); } finally { setIsDeleting(false); diff --git a/contexts/UserContext.tsx b/contexts/UserContext.tsx index 4fb8a4f..e6b5200 100644 --- a/contexts/UserContext.tsx +++ b/contexts/UserContext.tsx @@ -22,7 +22,7 @@ interface UserContextType { ) => Promise; loginWithOAuth: ( provider: 'google' | 'github', - userData: any + userData: { email: string; username: string; avatar?: string } ) => Promise; changePassword: ( currentPassword: string, @@ -68,22 +68,22 @@ export const UserProvider: React.FC<{ children: ReactNode }> = ({ // Use auth service for password-based login const result = await authService.login({ email, password }); - console.log('Login result received:', result); - console.log('User from login:', result.user); - console.log('User _id:', result.user._id); + console.warn('Login result received:', result); + console.warn('User from login:', result.user); + console.warn('User _id:', result.user._id); // Update last login time const updatedUser = { ...result.user, lastLoginAt: new Date() }; await dbService.updateUser(updatedUser); - console.log('Updated user with last login:', updatedUser); + console.warn('Updated user with last login:', updatedUser); // Store access token for subsequent API calls. localStorage.setItem('access_token', result.accessToken); // Set the user from the login result setUser(updatedUser); - console.log('User set in context'); + console.warn('User set in context'); return true; } catch (error) { console.error('Login error:', error); @@ -97,7 +97,7 @@ export const UserProvider: React.FC<{ children: ReactNode }> = ({ username?: string ): Promise => { try { - const result = await authService.register(email, password, username); + await authService.register(email, password, username); // Don't auto-login after registration, require email verification return true; } catch (error) { @@ -108,25 +108,25 @@ export const UserProvider: React.FC<{ children: ReactNode }> = ({ const loginWithOAuth = async ( provider: 'google' | 'github', - userData: any + userData: { email: string; username: string; avatar?: string } ): Promise => { try { const result = await authService.loginWithOAuth(provider, userData); - console.log('OAuth login result received:', result); - console.log('OAuth user:', result.user); - console.log('OAuth user _id:', result.user._id); + console.warn('OAuth login result received:', result); + console.warn('OAuth user:', result.user); + console.warn('OAuth user _id:', result.user._id); // Update last login time const updatedUser = { ...result.user, lastLoginAt: new Date() }; await dbService.updateUser(updatedUser); - console.log('Updated OAuth user with last login:', updatedUser); + console.warn('Updated OAuth user with last login:', updatedUser); localStorage.setItem('access_token', result.accessToken); setUser(updatedUser); - console.log('OAuth user set in context'); + console.warn('OAuth user set in context'); return true; } catch (error) { console.error('OAuth login error:', error); diff --git a/docker/Dockerfile b/docker/Dockerfile index 97c225d..d5865cf 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -62,8 +62,8 @@ RUN bun run build # Production stage - serve with nginx FROM nginx:alpine -# Install curl for health checks -RUN apk add --no-cache curl +# Install wget for health checks +RUN apk add --no-cache wget # Copy built files from builder stage COPY --from=builder /app/dist /usr/share/nginx/html @@ -78,8 +78,7 @@ RUN chown -R nginx:nginx /usr/share/nginx/html && \ chown -R nginx:nginx /etc/nginx/conf.d # Add health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD curl -f http://localhost/ || exit 1 + # Expose port 80 EXPOSE 80 diff --git a/docker/docker-bake.hcl b/docker/docker-bake.hcl index 23c1027..61d5b98 100644 --- a/docker/docker-bake.hcl +++ b/docker/docker-bake.hcl @@ -44,40 +44,40 @@ target "app" { "linux/amd64", "linux/arm64" ] - + tags = [ "${REGISTRY}rxminder:${TAG}", "${REGISTRY}rxminder:latest" ] - + args = { # CouchDB Configuration VITE_COUCHDB_URL = "${VITE_COUCHDB_URL}" VITE_COUCHDB_USER = "${VITE_COUCHDB_USER}" VITE_COUCHDB_PASSWORD = "${VITE_COUCHDB_PASSWORD}" - + # Application Configuration APP_BASE_URL = "${APP_BASE_URL}" - + # OAuth Configuration (Optional) VITE_GOOGLE_CLIENT_ID = "${VITE_GOOGLE_CLIENT_ID}" VITE_GITHUB_CLIENT_ID = "${VITE_GITHUB_CLIENT_ID}" - + # Build environment NODE_ENV = "production" } - + # Advanced buildx features cache-from = [ "type=gha", "type=registry,ref=${REGISTRY}rxminder:buildcache" ] - + cache-to = [ "type=gha,mode=max", "type=registry,ref=${REGISTRY}rxminder:buildcache,mode=max" ] - + # Attestations for supply chain security attest = [ "type=provenance,mode=max", diff --git a/docs/APP_NAME_CONFIGURATION.md b/docs/APP_NAME_CONFIGURATION.md index e7d0e26..3c7f7e1 100644 --- a/docs/APP_NAME_CONFIGURATION.md +++ b/docs/APP_NAME_CONFIGURATION.md @@ -72,24 +72,28 @@ APP_NAME=StagingApp ### Files That Use APP_NAME 1. **Frontend Files**: - - `index.html.template` - Page title - - `App.tsx` - UI header text - - `vite.config.ts` - Environment variable mapping + +- `index.html.template` - Page title +- `App.tsx` - UI header text +- `vite.config.ts` - Environment variable mapping 2. **Docker Files**: - - `docker/Dockerfile` - Build argument and environment variable - - `docker/docker-compose.yaml` - Build args and labels + +- `docker/Dockerfile` - Build argument and environment variable +- `docker/docker-compose.yaml` - Build args and labels 3. **Kubernetes Templates**: - - `k8s/frontend-deployment.yaml.template` - Resource names and labels - - `k8s/configmap.yaml.template` - Resource names and labels - - All other `k8s/*.yaml.template` files + +- `k8s/frontend-deployment.yaml.template` - Resource names and labels +- `k8s/configmap.yaml.template` - Resource names and labels +- All other `k8s/*.yaml.template` files 4. **Scripts**: - - `scripts/deploy.sh` - Container and image naming - - `scripts/buildx-helper.sh` - Container and image naming - - `scripts/validate-deployment.sh` - Container and image naming - - `scripts/process-html.sh` - HTML template processing + +- `scripts/deploy.sh` - Container and image naming +- `scripts/buildx-helper.sh` - Container and image naming +- `scripts/validate-deployment.sh` - Container and image naming +- `scripts/process-html.sh` - HTML template processing ## Build Process diff --git a/eslint.config.cjs b/eslint.config.cjs index 9fe1f61..3136981 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -3,6 +3,9 @@ const tsPlugin = require('@typescript-eslint/eslint-plugin'); const reactHooksPlugin = require('eslint-plugin-react-hooks'); module.exports = [ + { + ignores: ['dist/**', 'node_modules/**', '**/*.min.js'], + }, { files: ['**/*.{js,jsx,ts,tsx}'], languageOptions: { @@ -29,7 +32,7 @@ module.exports = [ // TypeScript ESLint rules '@typescript-eslint/no-unused-vars': [ 'error', - { argsIgnorePattern: '^_' }, + { argsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, ], '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/explicit-function-return-type': 'off', @@ -60,4 +63,10 @@ module.exports = [ 'no-console': 'off', }, }, + { + files: ['**/tests/e2e/fixtures.ts'], + rules: { + 'react-hooks/rules-of-hooks': 'off', + }, + }, ]; diff --git a/hooks/useLocalStorage.ts b/hooks/useLocalStorage.ts index 97b3e69..fe87fc8 100644 --- a/hooks/useLocalStorage.ts +++ b/hooks/useLocalStorage.ts @@ -8,7 +8,7 @@ function getStoredValue(key: string, defaultValue: T): T { const saved = localStorage.getItem(key); try { return saved ? JSON.parse(saved) : defaultValue; - } catch (e) { + } catch { return defaultValue; } } diff --git a/index.html b/index.html index 2d19c74..ad2d6c4 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - MyCustomMeds + diff --git a/package.json b/package.json index 1d9c58a..a8accb1 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "${APP_NAME:-rxminder}", + "name": "rxminder", "private": true, "version": "0.0.0", "type": "module", @@ -56,6 +56,8 @@ }, "dependencies": { "bcryptjs": "^3.0.2", + "express": "^5.1.0", + "jsonwebtoken": "^9.0.2", "react": "^19.1.1", "react-dom": "^19.1.1", "uuid": "^12.0.0" diff --git a/rename-app.sh b/rename-app.sh index 9214131..6577bd1 100644 --- a/rename-app.sh +++ b/rename-app.sh @@ -10,31 +10,31 @@ echo "🚀 Starting deploymif docker compose -f docker/docker-compose.yaml -p rx else print_error "Docker Compose services failed to start" docker compose -f docker/docker-compose.yaml -p rxminder-validation logsalidation..." - + # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color - + # Function to print colored output print_status() { echo -e "${BLUE}[INFO]${NC} $1" } - + print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } - + print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } - + print_error() { echo -e "${RED}[ERROR]${NC} $1" } - + # Cleanup function cleanup() { print_status "Cleaning up test containers..." @@ -42,48 +42,48 @@ else docker rm rxminder-validation-test 2>/dev/null || true docker compose -f docker/docker-compose.yaml -p rxminder-validation down 2>/dev/null || true } - + # Set trap for cleanup trap cleanup EXIT - + print_status "1. Validating environment files..." - + # Check if required environment files exist if [[ ! -f .env ]]; then print_error ".env file not found. Run 'cp .env.example .env' and configure it." exit 1 fi - + if [[ ! -f .env.example ]]; then print_error ".env.example file not found." exit 1 fi - + print_success "Environment files exist" - + # Validate environment consistency print_status "2. Checking environment variable consistency..." ./validate-env.sh - + print_status "3. Setting up Docker Buildx..." - + # Ensure buildx is available if ! docker buildx version >/dev/null 2>&1; then print_error "Docker Buildx is not available. Please update Docker to a version that supports Buildx." exit 1 fi - + # Create a new builder instance if it doesn't exist if ! docker buildx ls | grep -q "rxminder-builder"; then print_status "Creating new buildx builder instance..." docker buildx create --name rxminder-builder --driver docker-container --bootstrap fi - + # Use the builder docker buildx use rxminder-builder - + print_status "4. Building multi-platform Docker image with buildx..." - + # Build the image with buildx for multiple platforms docker buildx build --no-cache \ --platform linux/amd64,linux/arm64 \ @@ -102,29 +102,29 @@ else -t rxminder-validation \ --load \ . - + print_success "Docker image built successfully" - + print_status "5. Testing container startup and health..." - + # Run container in background docker run --rm -d \ -p 8083:80 \ --name rxminder-validation-test \ rxminder-validation - + # Wait for container to start sleep 5 - + # Check if container is running if ! docker ps | grep -q rxminder-validation-test; then print_error "Container failed to start" docker logs rxminder-validation-test exit 1 fi - + print_success "Container started successfully" - + # Test health endpoint print_status "5. Testing health endpoint..." for i in {1..10}; do @@ -139,7 +139,7 @@ else sleep 2 fi done - + # Test main application print_status "6. Testing main application..." HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8083) @@ -149,20 +149,20 @@ else print_error "Main application not responding properly (HTTP $HTTP_CODE)" exit 1 fi - + # Test docker-compose build print_status "7. Testing Docker Compose build..." docker compose -f docker/docker-compose.yaml build frontend --no-cache - + print_success "Docker Compose build successful" - + # Test docker-compose with validation project name print_status "8. Testing Docker Compose deployment..." docker compose -f docker/docker-compose.yaml -p rxminder-validation up -d --build - + # Wait for services to start sleep 10 - + # Check service health if docker compose -f docker/docker-compose.yaml -p meds-validation ps | grep -q "Up"; then print_success "Docker Compose services started successfully" @@ -171,20 +171,20 @@ else docker compose -f docker/docker-compose.yaml -p meds-validation logs exit 1 fi - + # Test health of compose deployment if curl -s -f http://localhost:8080/health > /dev/null; then print_success "Docker Compose health endpoint responding" else print_warning "Docker Compose health endpoint not responding (may need CouchDB)" fi - + print_status "9. Checking image size..." IMAGE_SIZE=$(docker image inspect rxminder-validation --format='{{.Size}}' | numfmt --to=iec) print_success "Image size: $IMAGE_SIZE" - + print_status "10. Validating security configuration..." - + # Check if image runs as non-root USER_INFO=$(docker run --rm rxminder-validation whoami) if [[ "$USER_INFO" != "root" ]]; then @@ -192,7 +192,7 @@ else else print_warning "Container runs as root user (security consideration)" fi - + # Check nginx configuration if docker run --rm rxminder-validation nginx -t 2>/dev/null; then print_success "Nginx configuration is valid" @@ -200,9 +200,9 @@ else print_error "Nginx configuration has issues" exit 1 fi - + print_status "11. Final validation complete!" - + echo echo "🎉 Deployment validation successful!" echo @@ -223,4 +223,4 @@ else echo "3. Set up monitoring and backups" echo "4. Configure SSL/TLS certificates" echo - \ No newline at end of file + diff --git a/scripts/gitea-deploy.sh b/scripts/gitea-deploy.sh index 2047881..5780adb 100755 --- a/scripts/gitea-deploy.sh +++ b/scripts/gitea-deploy.sh @@ -10,31 +10,31 @@ echo "🚀 Starting deploymif docker compose -f docker/docker-compose.yaml -p rx else print_error "Docker Compose services failed to start" docker compose -f docker/docker-compose.yaml -p rxminder-validation logsalidation..." - + # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color - + # Function to print colored output print_status() { echo -e "${BLUE}[INFO]${NC} $1" } - + print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } - + print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } - + print_error() { echo -e "${RED}[ERROR]${NC} $1" } - + # Cleanup function cleanup() { print_status "Cleaning up test containers..." @@ -42,48 +42,48 @@ else docker rm rxminder-validation-test 2>/dev/null || true docker compose -f docker/docker-compose.yaml -p rxminder-validation down 2>/dev/null || true } - + # Set trap for cleanup trap cleanup EXIT - + print_status "1. Validating environment files..." - + # Check if required environment files exist if [[ ! -f .env ]]; then print_error ".env file not found. Run 'cp .env.example .env' and configure it." exit 1 fi - + if [[ ! -f .env.example ]]; then print_error ".env.example file not found." exit 1 fi - + print_success "Environment files exist" - + # Validate environment consistency print_status "2. Checking environment variable consistency..." ./validate-env.sh - + print_status "3. Setting up Docker Buildx..." - + # Ensure buildx is available if ! docker buildx version >/dev/null 2>&1; then print_error "Docker Buildx is not available. Please update Docker to a version that supports Buildx." exit 1 fi - + # Create a new builder instance if it doesn't exist if ! docker buildx ls | grep -q "rxminder-builder"; then print_status "Creating new buildx builder instance..." docker buildx create --name rxminder-builder --driver docker-container --bootstrap fi - + # Use the builder docker buildx use rxminder-builder - + print_status "4. Building multi-platform Docker image with buildx..." - + # Build the image with buildx for multiple platforms docker buildx build --no-cache \ --platform linux/amd64,linux/arm64 \ @@ -102,29 +102,29 @@ else -t rxminder-validation \ --load \ . - + print_success "Docker image built successfully" - + print_status "5. Testing container startup and health..." - + # Run container in background docker run --rm -d \ -p 8083:80 \ --name rxminder-validation-test \ rxminder-validation - + # Wait for container to start sleep 5 - + # Check if container is running if ! docker ps | grep -q rxminder-validation-test; then print_error "Container failed to start" docker logs rxminder-validation-test exit 1 fi - + print_success "Container started successfully" - + # Test health endpoint print_status "5. Testing health endpoint..." for i in {1..10}; do @@ -139,7 +139,7 @@ else sleep 2 fi done - + # Test main application print_status "6. Testing main application..." HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8083) @@ -149,20 +149,20 @@ else print_error "Main application not responding properly (HTTP $HTTP_CODE)" exit 1 fi - + # Test docker-compose build print_status "7. Testing Docker Compose build..." docker compose -f docker/docker-compose.yaml build frontend --no-cache - + print_success "Docker Compose build successful" - + # Test docker-compose with validation project name print_status "8. Testing Docker Compose deployment..." docker compose -f docker/docker-compose.yaml -p rxminder-validation up -d --build - + # Wait for services to start sleep 10 - + # Check service health if docker compose -f docker/docker-compose.yaml -p meds-validation ps | grep -q "Up"; then print_success "Docker Compose services started successfully" @@ -171,20 +171,20 @@ else docker compose -f docker/docker-compose.yaml -p meds-validation logs exit 1 fi - + # Test health of compose deployment if curl -s -f http://localhost:8080/health > /dev/null; then print_success "Docker Compose health endpoint responding" else print_warning "Docker Compose health endpoint not responding (may need CouchDB)" fi - + print_status "9. Checking image size..." IMAGE_SIZE=$(docker image inspect rxminder-validation --format='{{.Size}}' | numfmt --to=iec) print_success "Image size: $IMAGE_SIZE" - + print_status "10. Validating security configuration..." - + # Check if image runs as non-root USER_INFO=$(docker run --rm rxminder-validation whoami) if [[ "$USER_INFO" != "root" ]]; then @@ -192,7 +192,7 @@ else else print_warning "Container runs as root user (security consideration)" fi - + # Check nginx configuration if docker run --rm rxminder-validation nginx -t 2>/dev/null; then print_success "Nginx configuration is valid" @@ -200,9 +200,9 @@ else print_error "Nginx configuration has issues" exit 1 fi - + print_status "11. Final validation complete!" - + echo echo "🎉 Deployment validation successful!" echo @@ -223,12 +223,12 @@ else echo "3. Set up monitoring and backups" echo "4. Configure SSL/TLS certificates" echo - + # Load Gitea-specific environment variables REGISTRY=${GITEA_SERVER_URL#https://} IMAGE_NAME=${GITEA_REPOSITORY} IMAGE_TAG=${GITEA_SHA:0:8} - + print_status "Registry: $REGISTRY" print_status "Image: $IMAGE_NAME:$IMAGE_TAG" fi @@ -236,7 +236,7 @@ fi # Check if .env file exists if [ ! -f ".env" ]; then print_warning ".env file not found, using defaults" - + # Create minimal .env for Gitea deployment cat > .env << EOF COUCHDB_USER=admin @@ -247,7 +247,7 @@ VITE_COUCHDB_PASSWORD=change-this-secure-password APP_BASE_URL=http://localhost:8080 NODE_ENV=production EOF - + print_warning "Created default .env file - please update with your credentials" fi @@ -269,31 +269,31 @@ print_success "Environment variables validated" # Function to deploy via Docker Compose deploy_compose() { print_status "Deploying with Docker Compose..." - + # Export image variables for compose export IMAGE_TAG export REGISTRY export IMAGE_NAME - + # Use the built image from registry if available if [ "$GITEA_ACTIONS" = "true" ]; then # Override the image in docker-compose export FRONTEND_IMAGE="$REGISTRY/$IMAGE_NAME:$IMAGE_TAG" print_status "Using Gitea Actions built image: $FRONTEND_IMAGE" fi - + # Pull the latest images print_status "Pulling latest images..." docker-compose -f docker/docker-compose.yaml pull || print_warning "Failed to pull some images" - + # Start services print_status "Starting services..." docker-compose -f docker/docker-compose.yaml up -d - + # Wait for services print_status "Waiting for services to be ready..." sleep 10 - + # Health check print_status "Checking service health..." if curl -f http://localhost:8080/health > /dev/null 2>&1; then @@ -302,7 +302,7 @@ deploy_compose() { print_warning "Frontend health check failed, checking logs..." docker-compose -f docker/docker-compose.yaml logs frontend fi - + if curl -f http://localhost:5984/_up > /dev/null 2>&1; then print_success "CouchDB service is healthy" else @@ -313,26 +313,26 @@ deploy_compose() { # Function to deploy via Kubernetes deploy_k8s() { print_status "Deploying to Kubernetes..." - + if ! command -v kubectl &> /dev/null; then print_error "kubectl is not installed" exit 1 fi - + # Update image in k8s manifests if [ "$GITEA_ACTIONS" = "true" ]; then print_status "Updating Kubernetes manifests with new image..." sed -i "s|image:.*rxminder.*|image: $REGISTRY/$IMAGE_NAME:$IMAGE_TAG|g" k8s/frontend-deployment.yaml fi - + # Apply manifests print_status "Applying Kubernetes manifests..." kubectl apply -f k8s/ - + # Wait for rollout print_status "Waiting for deployment rollout..." kubectl rollout status deployment/frontend-deployment - + print_success "Kubernetes deployment completed" } diff --git a/scripts/gitea-helper.sh b/scripts/gitea-helper.sh index 898c275..d2a8122 100755 --- a/scripts/gitea-helper.sh +++ b/scripts/gitea-helper.sh @@ -46,12 +46,12 @@ echo "🚀 Deploying RxMinder from Gitea to $ENVIRONMENT environment..." # Check if running in Gitea Actions if [ "$GITEA_ACTIONS" = "true" ]; then print_status "Running in Gitea Actions environment" - + # Load Gitea-specific environment variables REGISTRY=${GITEA_SERVER_URL#https://} IMAGE_NAME=${GITEA_REPOSITORY} IMAGE_TAG=${GITEA_SHA:0:8} - + print_status "Registry: $REGISTRY" print_status "Image: $IMAGE_NAME:$IMAGE_TAG" fi @@ -59,7 +59,7 @@ fi # Check if .env file exists if [ ! -f ".env" ]; then print_warning ".env file not found, using defaults" - + # Create minimal .env for Gitea deployment cat > .env << EOF COUCHDB_USER=admin @@ -70,7 +70,7 @@ VITE_COUCHDB_PASSWORD=change-this-secure-password APP_BASE_URL=http://localhost:8080 NODE_ENV=production EOF - + print_warning "Created default .env file - please update with your credentials" fi @@ -92,31 +92,31 @@ print_success "Environment variables validated" # Function to deploy via Docker Compose deploy_compose() { print_status "Deploying with Docker Compose..." - + # Export image variables for compose export IMAGE_TAG export REGISTRY export IMAGE_NAME - + # Use the built image from registry if available if [ "$GITEA_ACTIONS" = "true" ]; then # Override the image in docker-compose export FRONTEND_IMAGE="$REGISTRY/$IMAGE_NAME:$IMAGE_TAG" print_status "Using Gitea Actions built image: $FRONTEND_IMAGE" fi - + # Pull the latest images print_status "Pulling latest images..." docker-compose -f docker/docker-compose.yaml pull || print_warning "Failed to pull some images" - + # Start services print_status "Starting services..." docker-compose -f docker/docker-compose.yaml up -d - + # Wait for services print_status "Waiting for services to be ready..." sleep 10 - + # Health check print_status "Checking service health..." if curl -f http://localhost:8080/health > /dev/null 2>&1; then @@ -125,7 +125,7 @@ deploy_compose() { print_warning "Frontend health check failed, checking logs..." docker-compose -f docker/docker-compose.yaml logs frontend fi - + if curl -f http://localhost:5984/_up > /dev/null 2>&1; then print_success "CouchDB service is healthy" else @@ -136,26 +136,26 @@ deploy_compose() { # Function to deploy via Kubernetes deploy_k8s() { print_status "Deploying to Kubernetes..." - + if ! command -v kubectl &> /dev/null; then print_error "kubectl is not installed" exit 1 fi - + # Update image in k8s manifests if [ "$GITEA_ACTIONS" = "true" ]; then print_status "Updating Kubernetes manifests with new image..." sed -i "s|image:.*rxminder.*|image: $REGISTRY/$IMAGE_NAME:$IMAGE_TAG|g" k8s/frontend-deployment.yaml fi - + # Apply manifests print_status "Applying Kubernetes manifests..." kubectl apply -f k8s/ - + # Wait for rollout print_status "Waiting for deployment rollout..." kubectl rollout status deployment/frontend-deployment - + print_success "Kubernetes deployment completed" } @@ -206,26 +206,26 @@ print_success "All tasks completed! 🚀" print_error "Docker is not installed" exit 1 fi - + # Check Docker Buildx if ! docker buildx version >/dev/null 2>&1; then print_error "Docker Buildx is not available" exit 1 fi - + # Check if in Gitea Actions environment if [ "$GITEA_ACTIONS" = "true" ]; then print_status "Running in Gitea Actions environment" GITEA_REGISTRY=${GITEA_SERVER_URL#https://} GITEA_REPOSITORY=${GITEA_REPOSITORY} fi - + print_success "All requirements met" } setup_buildx() { print_status "Setting up Docker Buildx for Gitea..." - + # Create builder if it doesn't exist if ! docker buildx ls | grep -q "gitea-builder"; then print_status "Creating Gitea buildx builder..." @@ -243,13 +243,13 @@ setup_buildx() { login_registry() { print_status "Logging into Gitea registry..." - + if [ -z "$GITEA_TOKEN" ]; then print_error "GITEA_TOKEN environment variable is required" print_status "Set it with: export GITEA_TOKEN=your_token" exit 1 fi - + # Login to Gitea registry echo "$GITEA_TOKEN" | docker login "$GITEA_REGISTRY" -u "$GITEA_ACTOR" --password-stdin print_success "Logged into Gitea registry" @@ -257,152 +257,152 @@ login_registry() { build_local() { print_status "Building for local development..." - + cd "$PROJECT_DIR" - + # Load environment variables if [ -f ".env" ]; then export $(cat .env | grep -v '^#' | xargs) fi - + # Build with Gitea bake file docker buildx bake \ -f .gitea/gitea-bake.hcl \ --set="*.platform=linux/amd64" \ --load \ dev - + print_success "Local build completed" } build_multiplatform() { local tag=${1:-$DEFAULT_TAG} print_status "Building multi-platform image with tag: $tag..." - + cd "$PROJECT_DIR" - + # Load environment variables if [ -f ".env" ]; then export $(cat .env | grep -v '^#' | xargs) fi - + # Export variables for bake export TAG="$tag" export GITEA_SHA=${GITEA_SHA:-$(git rev-parse --short HEAD 2>/dev/null || echo "dev")} - + # Build with Gitea bake file docker buildx bake \ -f .gitea/gitea-bake.hcl \ app-ci - + print_success "Multi-platform build completed" } build_staging() { print_status "Building staging image..." - + cd "$PROJECT_DIR" - + # Load environment variables if [ -f ".env.staging" ]; then export $(cat .env.staging | grep -v '^#' | xargs) elif [ -f ".env" ]; then export $(cat .env | grep -v '^#' | xargs) fi - + # Export variables for bake export TAG="staging-$(date +%Y%m%d-%H%M%S)" export GITEA_SHA=${GITEA_SHA:-$(git rev-parse --short HEAD 2>/dev/null || echo "staging")} - + # Build staging target docker buildx bake \ -f .gitea/gitea-bake.hcl \ staging - + print_success "Staging build completed" } build_production() { local tag=${1:-$DEFAULT_TAG} print_status "Building production image with tag: $tag..." - + cd "$PROJECT_DIR" - + # Load production environment variables if [ -f ".env.production" ]; then export $(cat .env.production | grep -v '^#' | xargs) elif [ -f ".env" ]; then export $(cat .env | grep -v '^#' | xargs) fi - + # Export variables for bake export TAG="$tag" export GITEA_SHA=${GITEA_SHA:-$(git rev-parse --short HEAD 2>/dev/null || echo "prod")} - + # Build production target with full attestations docker buildx bake \ -f .gitea/gitea-bake.hcl \ prod - + print_success "Production build completed" } test_local() { print_status "Running tests locally..." - + cd "$PROJECT_DIR" - + # Install dependencies if needed if [ ! -d "node_modules" ]; then print_status "Installing dependencies..." bun install --frozen-lockfile fi - + # Run linting print_status "Running linter..." bun run lint - + # Run type checking print_status "Running type checker..." bun run type-check - + # Run tests print_status "Running tests..." bun run test - + print_success "All tests passed" } deploy() { local environment=${1:-production} local tag=${2:-latest} - + print_status "Deploying to $environment with tag $tag..." - + # Use the gitea-deploy script "$SCRIPT_DIR/gitea-deploy.sh" "$environment" "$tag" } cleanup() { print_status "Cleaning up Gitea builder and images..." - + # Remove builder if docker buildx ls | grep -q "gitea-builder"; then docker buildx rm gitea-builder print_success "Gitea builder removed" fi - + # Clean up old images (keep last 3 tags) print_status "Cleaning up old images..." docker image prune -f --filter "until=72h" || print_warning "Image cleanup failed" - + print_success "Cleanup completed" } show_status() { print_status "Gitea CI/CD Status" echo - + # Check environment if [ "$GITEA_ACTIONS" = "true" ]; then echo "🏃 Running in Gitea Actions" @@ -415,13 +415,13 @@ show_status() { echo "đŸ“Ļ Registry: $GITEA_REGISTRY" echo "📋 Repository: $GITEA_REPOSITORY" fi - + echo - + # Check Docker and buildx echo "đŸŗ Docker version: $(docker --version)" echo "🔧 Buildx version: $(docker buildx version)" - + # Check builders echo echo "đŸ—ī¸ Available builders:" diff --git a/scripts/k8s-deploy-template.sh b/scripts/k8s-deploy-template.sh index d8c9a70..c48bfcf 100755 --- a/scripts/k8s-deploy-template.sh +++ b/scripts/k8s-deploy-template.sh @@ -52,9 +52,9 @@ load_env() { substitute_template() { local template_file="$1" local output_file="$2" - + print_status "Processing template: $template_file" - + # Use envsubst to substitute environment variables if command -v envsubst >/dev/null 2>&1; then envsubst < "$template_file" > "$output_file" @@ -62,14 +62,14 @@ substitute_template() { print_error "envsubst not found. Please install gettext package." exit 1 fi - + print_success "Generated: $output_file" } # Function to apply Kubernetes resources apply_k8s_resource() { local resource_file="$1" - + if [[ -f "$resource_file" ]]; then print_status "Applying Kubernetes resource: $resource_file" if kubectl apply -f "$resource_file"; then @@ -94,15 +94,15 @@ validate_env() { "STORAGE_CLASS" "STORAGE_SIZE" ) - + local missing_vars=() - + for var in "${required_vars[@]}"; do if [[ -z "${!var:-}" ]]; then missing_vars+=("$var") fi done - + if [[ ${#missing_vars[@]} -gt 0 ]]; then print_error "Missing required environment variables:" for var in "${missing_vars[@]}"; do @@ -111,7 +111,7 @@ validate_env() { print_warning "Please update your .env file with these variables." exit 1 fi - + print_success "All required environment variables are set" } @@ -119,22 +119,22 @@ validate_env() { process_templates() { local temp_dir="/tmp/rxminder-k8s-$$" mkdir -p "$temp_dir" - + print_status "Processing Kubernetes templates..." - + # Find all template files local template_files=( "$K8S_DIR/couchdb-secret.yaml.template" "$K8S_DIR/ingress.yaml.template" ) - + # Add any additional template files for template_file in "$K8S_DIR"/*.template; do if [[ -f "$template_file" ]]; then template_files+=("$template_file") fi done - + # Process each template for template_file in "${template_files[@]}"; do if [[ -f "$template_file" ]]; then @@ -144,16 +144,16 @@ process_templates() { substitute_template "$template_file" "$output_file" fi done - + echo "$temp_dir" } # Function to deploy resources in correct order deploy_resources() { local resource_dir="$1" - + print_status "Deploying Kubernetes resources..." - + # Deploy in specific order for dependencies local deployment_order=( "couchdb-secret.yaml" @@ -167,7 +167,7 @@ deploy_resources() { "$K8S_DIR/network-policy.yaml" "$K8S_DIR/hpa.yaml" ) - + for resource in "${deployment_order[@]}"; do if [[ "$resource" == *.yaml ]]; then # Check if it's a template-generated file @@ -184,11 +184,11 @@ deploy_resources() { # Function to run database seeding job run_db_seed() { print_status "Running database seed job..." - + # Apply the db-seed-job (which uses environment variables from secret) if kubectl apply -f "$K8S_DIR/db-seed-job.yaml"; then print_success "Database seed job submitted" - + # Wait for job completion print_status "Waiting for database seed job to complete..." if kubectl wait --for=condition=complete --timeout=300s job/db-seed-job; then @@ -207,19 +207,19 @@ run_db_seed() { show_status() { print_status "Deployment Status:" echo - + print_status "Pods:" kubectl get pods -l app="${APP_NAME:-rxminder}" echo - + print_status "Services:" kubectl get services -l app="${APP_NAME:-rxminder}" echo - + print_status "Ingress:" kubectl get ingress echo - + if [[ -n "${INGRESS_HOST:-}" ]]; then print_success "Application should be available at: http://${INGRESS_HOST}" fi @@ -235,12 +235,12 @@ cleanup() { # Main deployment function main() { local command="${1:-deploy}" - + case "$command" in "deploy"|"apply") print_status "🚀 Starting RxMinder Kubernetes deployment..." echo - + # Set default values for required variables export APP_NAME="${APP_NAME:-rxminder}" export DOCKER_IMAGE="${DOCKER_IMAGE:-gitea-http.taildb3494.ts.net/will/meds:latest}" @@ -249,39 +249,39 @@ main() { export INGRESS_HOST="${INGRESS_HOST:-rxminder.local}" export STORAGE_CLASS="${STORAGE_CLASS:-longhorn}" export STORAGE_SIZE="${STORAGE_SIZE:-5Gi}" - + load_env validate_env - + # Process templates temp_dir=$(process_templates) trap cleanup EXIT - + # Deploy resources deploy_resources "$temp_dir" - + # Run database seeding run_db_seed - + # Show status echo show_status - + print_success "🎉 RxMinder deployment completed!" ;; - + "status") load_env show_status ;; - + "delete"|"cleanup") print_status "đŸ—‘ī¸ Cleaning up RxMinder deployment..." kubectl delete all,pvc,secret,configmap,ingress -l app="${APP_NAME:-rxminder}" || true kubectl delete job db-seed-job || true print_success "Cleanup completed" ;; - + "help"|"-h"|"--help") echo "RxMinder Kubernetes Deployment Script" echo @@ -302,7 +302,7 @@ main() { echo " STORAGE_CLASS Storage class for PVCs (default: longhorn)" echo " STORAGE_SIZE Storage size for database (default: 5Gi)" ;; - + *) print_error "Unknown command: $command" echo "Use '$0 help' for usage information" diff --git a/scripts/pre-commit-checks.sh b/scripts/pre-commit-checks.sh index d744477..1d78934 100755 --- a/scripts/pre-commit-checks.sh +++ b/scripts/pre-commit-checks.sh @@ -69,7 +69,9 @@ run_check "prettier" "bun run pre-commit" # 2. ESLint on staged JS/TS files only STAGED_JS_TS_FILES=$(echo "$STAGED_FILES" | grep -E '\.(js|jsx|ts|tsx)$' || true) if [ -n "$STAGED_JS_TS_FILES" ]; then - run_check "eslint" "bunx eslint --fix --max-warnings 0 $STAGED_JS_TS_FILES" + # Convert newlines to spaces for proper argument passing + ESLINT_FILES=$(echo "$STAGED_JS_TS_FILES" | tr '\n' ' ') + run_check "eslint" "bunx eslint --fix --max-warnings 0 $ESLINT_FILES" else echo "0" > "$TEMP_DIR/eslint.exit" echo "No JS/TS files to lint" > "$TEMP_DIR/eslint.out" @@ -87,7 +89,9 @@ fi # 4. Markdown linting on staged markdown files STAGED_MD_FILES=$(echo "$STAGED_FILES" | grep -E '\.md$' || true) if [ -n "$STAGED_MD_FILES" ]; then - run_check "markdown" "bunx markdownlint-cli2 --fix $STAGED_MD_FILES || echo 'Markdown linting failed but continuing...'" + # Convert newlines to spaces for proper argument passing + MARKDOWN_FILES=$(echo "$STAGED_MD_FILES" | tr '\n' ' ') + run_check "markdown" "bunx markdownlint-cli2 --fix $MARKDOWN_FILES || echo 'Markdown linting failed but continuing...'" else echo "0" > "$TEMP_DIR/markdown.exit" echo "No markdown files to lint" > "$TEMP_DIR/markdown.out" @@ -95,7 +99,9 @@ fi # 5. Secret scanning on staged files (optional check) if command -v secretlint > /dev/null; then - run_check "secrets" "bunx secretlint $STAGED_FILES || echo 'Secret scanning failed but continuing...'" + # Convert newlines to spaces for proper argument passing + SECRET_FILES=$(echo "$STAGED_FILES" | tr '\n' ' ') + run_check "secrets" "bunx secretlint $SECRET_FILES || echo 'Secret scanning failed but continuing...'" else echo "0" > "$TEMP_DIR/secrets.exit" echo "secretlint not available, skipping secret scanning" > "$TEMP_DIR/secrets.out" diff --git a/scripts/seed-production.js b/scripts/seed-production.js index cfe894a..ad986ae 100644 --- a/scripts/seed-production.js +++ b/scripts/seed-production.js @@ -11,14 +11,14 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const projectDir = resolve(__dirname, '..'); -console.log('🌱 Starting production database seeding...'); +console.warn('🌱 Starting production database seeding...'); // Load environment variables from .env file if it exists try { const envFile = resolve(projectDir, '.env'); const envContent = readFileSync(envFile, 'utf8'); - console.log('📄 Loading environment variables from .env file...'); + console.warn('📄 Loading environment variables from .env file...'); envContent.split('\n').forEach(line => { const trimmed = line.trim(); @@ -30,8 +30,8 @@ try { } } }); -} catch (error) { - console.log( +} catch (_error) { + console.warn( 'â„šī¸ No .env file found, using environment variables or defaults' ); } @@ -53,10 +53,10 @@ process.env.VITE_COUCHDB_URL = COUCHDB_URL; process.env.VITE_COUCHDB_USER = COUCHDB_USER; process.env.VITE_COUCHDB_PASSWORD = COUCHDB_PASSWORD; -console.log('🔗 CouchDB Configuration:'); -console.log(` URL: ${COUCHDB_URL}`); -console.log(` User: ${COUCHDB_USER}`); -console.log(` Password: ${'*'.repeat(COUCHDB_PASSWORD.length)}`); +console.warn('🔗 CouchDB Configuration:'); +console.warn(` URL: ${COUCHDB_URL}`); +console.warn(` User: ${COUCHDB_USER}`); +console.warn(` Password: ${'*'.repeat(COUCHDB_PASSWORD.length)}`); // Validate required environment variables if (!COUCHDB_URL || !COUCHDB_USER || !COUCHDB_PASSWORD) { @@ -75,18 +75,18 @@ async function seedDatabase() { const { DatabaseSeeder } = await import('../services/database.seeder.ts'); // Wait a bit for databases to be initialized - console.log('âŗ Waiting for databases to initialize...'); + console.warn('âŗ Waiting for databases to initialize...'); await new Promise(resolve => setTimeout(resolve, 2000)); const seeder = new DatabaseSeeder(); - console.log('📊 Seeding admin user...'); + console.warn('📊 Seeding admin user...'); await seeder.seedDefaultAdmin(); - console.log('🎉 Production database seeding completed successfully!'); - console.log('🔐 You can now login with:'); - console.log(' Email: admin@localhost'); - console.log(' Password: change-this-secure-password'); + console.warn('🎉 Production database seeding completed successfully!'); + console.warn('🔐 You can now login with:'); + console.warn(' Email: admin@localhost'); + console.warn(' Password: change-this-secure-password'); process.exit(0); } catch (error) { diff --git a/scripts/undeploy-k8s.sh b/scripts/undeploy-k8s.sh index 50284cb..6f399d8 100755 --- a/scripts/undeploy-k8s.sh +++ b/scripts/undeploy-k8s.sh @@ -52,21 +52,21 @@ load_env() { # Function to substitute environment variables in template files substitute_templates() { print_info "Processing template files..." - + # Create temporary directory mkdir -p "$TEMP_DIR" - + # Process each template file for template_file in "$K8S_DIR"/*.template; do if [[ -f "$template_file" ]]; then local filename=$(basename "$template_file" .template) local output_file="$TEMP_DIR/$filename" - + print_info "Processing template: $filename" - + # Substitute environment variables envsubst < "$template_file" > "$output_file" - + print_success "Generated: $output_file" fi done @@ -76,13 +76,13 @@ substitute_templates() { validate_env() { local required_vars=("INGRESS_HOST") local missing_vars=() - + for var in "${required_vars[@]}"; do if [[ -z "${!var:-}" ]]; then missing_vars+=("$var") fi done - + if [[ ${#missing_vars[@]} -gt 0 ]]; then print_error "Missing required environment variables:" for var in "${missing_vars[@]}"; do @@ -108,15 +108,15 @@ ensure_namespace() { # Function to apply Kubernetes manifests apply_manifests() { local manifest_dir="$1" - + print_info "Applying Kubernetes manifests from $manifest_dir" - + # Apply static files that don't have template counterparts for manifest_file in "$K8S_DIR"/*.yaml; do if [[ -f "$manifest_file" && ! "$manifest_file" =~ \.template$ ]]; then local basename_file=$(basename "$manifest_file") local template_file="$K8S_DIR/${basename_file}.template" - + # Only apply if there's no corresponding template file if [[ ! -f "$template_file" ]]; then print_info "Applying static file: $basename_file" @@ -124,7 +124,7 @@ apply_manifests() { fi fi done - + # Apply processed template files if [[ -d "$TEMP_DIR" ]]; then for manifest_file in "$TEMP_DIR"/*.yaml; do @@ -148,19 +148,19 @@ cleanup() { show_status() { print_info "Deployment Status:" echo "" - + print_info "Pods:" kubectl get pods -l app=rxminder -n "$NAMESPACE" echo "" - + print_info "Services:" kubectl get services -l app=rxminder -n "$NAMESPACE" echo "" - + print_info "Ingress:" kubectl get ingress -l app=rxminder -n "$NAMESPACE" echo "" - + if [[ -n "${INGRESS_HOST:-}" ]]; then print_success "Application should be available at: http://$INGRESS_HOST" fi @@ -190,7 +190,7 @@ main() { local dry_run=false local status_only=false local cleanup_only=false - + # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in @@ -221,54 +221,54 @@ main() { ;; esac done - + # Handle cleanup only if [[ "$cleanup_only" == true ]]; then cleanup exit 0 fi - + # Handle status only if [[ "$status_only" == true ]]; then show_status exit 0 fi - + # Check if kubectl is available if ! command -v kubectl &> /dev/null; then print_error "kubectl is not installed or not in PATH" exit 1 fi - + # Check if we can connect to Kubernetes cluster if ! kubectl cluster-info &> /dev/null; then print_error "Cannot connect to Kubernetes cluster" print_info "Make sure your kubectl is configured correctly" exit 1 fi - + print_info "🚀 Deploying Medication Reminder App to Kubernetes" echo "" - + # Load environment variables load_env "$env_file" - + # Set default namespace if not provided in environment NAMESPACE="${NAMESPACE:-rxminder}" - + # Validate required environment variables validate_env - + # Ensure namespace exists ensure_namespace - + # Process templates substitute_templates - + if [[ "$dry_run" == true ]]; then print_info "Dry run mode - showing generated manifests:" echo "" - + for manifest_file in "$TEMP_DIR"/*.yaml; do if [[ -f "$manifest_file" ]]; then echo "=== $(basename "$manifest_file") ===" @@ -279,14 +279,14 @@ main() { else # Apply manifests apply_manifests "$K8S_DIR" - + print_success "Deployment completed!" echo "" - + # Show status show_status fi - + # Cleanup cleanup } diff --git a/scripts/validate-env.sh b/scripts/validate-env.sh index b572b22..20ca405 100755 --- a/scripts/validate-env.sh +++ b/scripts/validate-env.sh @@ -55,17 +55,17 @@ OPTIONAL_VARS=( validate_file() { local file="$1" local file_type="$2" - + print_section "Validating $file ($file_type)" - + if [[ ! -f "$file" ]]; then print_error "File not found: $file" return 1 fi - + local missing_vars=() local found_vars=() - + # Check core variables for var in "${CORE_VARS[@]}"; do if grep -q "^${var}=" "$file" || grep -q "^#.*${var}=" "$file"; then @@ -74,7 +74,7 @@ validate_file() { missing_vars+=("$var") fi done - + # Check K8s variables for relevant files if [[ "$file_type" != "template" ]]; then for var in "${K8S_VARS[@]}"; do @@ -85,22 +85,22 @@ validate_file() { fi done fi - + # Report results print_success "Found ${#found_vars[@]} variables" - + if [[ ${#missing_vars[@]} -gt 0 ]]; then print_warning "Missing variables:" for var in "${missing_vars[@]}"; do echo " - $var" done fi - + # Check for old VITE_MAILGUN variables if grep -q "VITE_MAILGUN" "$file"; then print_error "Found deprecated VITE_MAILGUN variables (should be MAILGUN_*)" fi - + # Check variable format local malformed_vars=() while IFS= read -r line; do @@ -111,25 +111,25 @@ validate_file() { fi fi done < "$file" - + if [[ ${#malformed_vars[@]} -gt 0 ]]; then print_warning "Malformed variable names:" for var in "${malformed_vars[@]}"; do echo " - $var" done fi - + echo "" } validate_consistency() { print_section "Cross-file Consistency Check" - + # Extract variable names from each file local example_vars=() local env_vars=() local prod_vars=() - + if [[ -f ".env.example" ]]; then while IFS= read -r line; do if [[ "$line" =~ ^[A-Z_]+=.* ]]; then @@ -137,7 +137,7 @@ validate_consistency() { fi done < ".env.example" fi - + if [[ -f ".env" ]]; then while IFS= read -r line; do if [[ "$line" =~ ^[A-Z_]+=.* ]]; then @@ -145,7 +145,7 @@ validate_consistency() { fi done < ".env" fi - + if [[ -f ".env.production" ]]; then while IFS= read -r line; do if [[ "$line" =~ ^[A-Z_]+=.* ]]; then @@ -153,11 +153,11 @@ validate_consistency() { fi done < ".env.production" fi - + # Check if .env and .env.production have all variables from .env.example local missing_in_env=() local missing_in_prod=() - + for var in "${example_vars[@]}"; do if [[ ! " ${env_vars[@]} " =~ " ${var} " ]]; then missing_in_env+=("$var") @@ -166,7 +166,7 @@ validate_consistency() { missing_in_prod+=("$var") fi done - + if [[ ${#missing_in_env[@]} -eq 0 ]]; then print_success ".env has all variables from .env.example" else @@ -175,7 +175,7 @@ validate_consistency() { echo " - $var" done fi - + if [[ ${#missing_in_prod[@]} -eq 0 ]]; then print_success ".env.production has all variables from .env.example" else @@ -184,20 +184,20 @@ validate_consistency() { echo " - $var" done fi - + echo "" } validate_k8s_template() { print_section "Kubernetes Template Validation" - + local template_file="k8s/ingress.yaml.template" - + if [[ ! -f "$template_file" ]]; then print_error "Template file not found: $template_file" return 1 fi - + # Check for template variables local template_vars=() while IFS= read -r line; do @@ -208,16 +208,16 @@ validate_k8s_template() { fi fi done < "$template_file" - + print_success "Found ${#template_vars[@]} template variables:" for var in "${template_vars[@]}"; do echo " - \${$var}" done - + # Check if template variables are defined in env files for var in "${template_vars[@]}"; do local found_in_files=() - + if grep -q "^${var}=" ".env.example" 2>/dev/null; then found_in_files+=(".env.example") fi @@ -227,39 +227,39 @@ validate_k8s_template() { if grep -q "^${var}=" ".env.production" 2>/dev/null; then found_in_files+=(".env.production") fi - + if [[ ${#found_in_files[@]} -gt 0 ]]; then print_success "$var defined in: ${found_in_files[*]}" else print_error "$var not defined in any environment file" fi done - + echo "" } main() { print_header - + # Validate individual files if [[ -f ".env.example" ]]; then validate_file ".env.example" "template" fi - + if [[ -f ".env" ]]; then validate_file ".env" "development" fi - + if [[ -f ".env.production" ]]; then validate_file ".env.production" "production" fi - + # Cross-file validation validate_consistency - + # Kubernetes template validation validate_k8s_template - + print_section "Summary" print_success "Environment validation complete!" echo "" diff --git a/services/auth/__tests__/auth.integration.test.ts b/services/auth/__tests__/auth.integration.test.ts index e4af9f9..2f74039 100644 --- a/services/auth/__tests__/auth.integration.test.ts +++ b/services/auth/__tests__/auth.integration.test.ts @@ -1,6 +1,5 @@ import { authService } from '../auth.service'; import { AccountStatus } from '../auth.constants'; -import { User } from '../../../types'; // Helper to clear localStorage and reset the mock DB before each test beforeEach(() => { diff --git a/services/auth/__tests__/emailVerification.test.ts b/services/auth/__tests__/emailVerification.test.ts index bbd0af6..6eaa0ae 100644 --- a/services/auth/__tests__/emailVerification.test.ts +++ b/services/auth/__tests__/emailVerification.test.ts @@ -1,5 +1,4 @@ import { EmailVerificationService } from '../emailVerification.service'; -import { dbService } from '../../couchdb.factory'; jest.mock('../../couchdb.factory'); jest.mock('../../email'); diff --git a/services/auth/auth.middleware.ts b/services/auth/auth.middleware.ts index cc53207..d4dc3f2 100644 --- a/services/auth/auth.middleware.ts +++ b/services/auth/auth.middleware.ts @@ -31,7 +31,7 @@ export const authenticate = ( }; // Security: Role-based authorization middleware -export const authorize = (...allowedRoles: string[]) => { +export const authorize = (..._allowedRoles: string[]) => { return (req: Request, res: Response, next: NextFunction) => { try { // Security: Check if user exists in request diff --git a/services/auth/auth.service.ts b/services/auth/auth.service.ts index 1804279..ae90e26 100644 --- a/services/auth/auth.service.ts +++ b/services/auth/auth.service.ts @@ -1,7 +1,5 @@ import { v4 as uuidv4 } from 'uuid'; import { dbService } from '../../services/couchdb.factory'; -import { AccountStatus } from './auth.constants'; -import { User } from '../../types'; import { AuthenticatedUser } from './auth.types'; import { EmailVerificationService } from './emailVerification.service'; @@ -33,17 +31,17 @@ const authService = { }, async login(input: { email: string; password: string }) { - console.log('🔐 Login attempt for:', input.email); + console.warn('🔐 Login attempt for:', input.email); // Find user by email const user = await dbService.findUserByEmail(input.email); if (!user) { - console.log('❌ User not found for email:', input.email); + console.warn('❌ User not found for email:', input.email); throw new Error('User not found'); } - console.log('👤 User found:', { + console.warn('👤 User found:', { email: user.email, hasPassword: !!user.password, role: user.role, @@ -53,25 +51,25 @@ const authService = { // Check if user has a password (email-based account) if (!user.password) { - console.log('❌ No password found - OAuth account'); + console.warn('❌ No password found - OAuth account'); throw new Error( 'This account was created with OAuth. Please use Google or GitHub to sign in.' ); } // Simple password verification (in production, use bcrypt) - console.log('🔍 Comparing passwords:', { + console.warn('🔍 Comparing passwords:', { inputPassword: input.password, storedPassword: user.password, match: user.password === input.password, }); if (user.password !== input.password) { - console.log('❌ Password mismatch'); + console.warn('❌ Password mismatch'); throw new Error('Invalid password'); } - console.log('✅ Login successful for:', user.email); + console.warn('✅ Login successful for:', user.email); // Return mock tokens for frontend compatibility return { @@ -204,7 +202,10 @@ const authService = { const resetTokens = JSON.parse( localStorage.getItem('password_reset_tokens') || '[]' ); - const resetToken = resetTokens.find((t: any) => t.token === token); + const resetToken = resetTokens.find( + (t: { token: string; userId: string; email: string; expiresAt: Date }) => + t.token === token + ); if (!resetToken) { throw new Error('Invalid or expired reset token'); @@ -227,7 +228,10 @@ const authService = { ); // Remove used token - const filteredTokens = resetTokens.filter((t: any) => t.token !== token); + const filteredTokens = resetTokens.filter( + (t: { token: string; userId: string; email: string; expiresAt: Date }) => + t.token !== token + ); localStorage.setItem( 'password_reset_tokens', JSON.stringify(filteredTokens) diff --git a/services/couchdb.factory.ts b/services/couchdb.factory.ts index a9159f2..fbb6a80 100644 --- a/services/couchdb.factory.ts +++ b/services/couchdb.factory.ts @@ -6,7 +6,7 @@ import { CouchDBService as MockCouchDBService } from './couchdb'; // Environment detection const isProduction = () => { // Check if we're in a Docker environment or if CouchDB URL is configured - const env = (import.meta as any).env || {}; + const env = (import.meta as { env?: Record }).env || {}; const couchdbUrl = env.VITE_COUCHDB_URL || (typeof process !== 'undefined' ? process.env.VITE_COUCHDB_URL : null) || @@ -20,7 +20,7 @@ const createDbService = () => { if (isProduction()) { try { // Use dynamic require to avoid TypeScript resolution issues - // eslint-disable-next-line @typescript-eslint/no-var-requires + const { CouchDBService: RealCouchDBService, } = require('./couchdb.production'); diff --git a/services/couchdb.production.ts b/services/couchdb.production.ts index aadb401..05076f6 100644 --- a/services/couchdb.production.ts +++ b/services/couchdb.production.ts @@ -18,11 +18,9 @@ export class CouchDBService { constructor() { // Get CouchDB configuration from environment - const couchdbUrl = - (import.meta as any).env?.VITE_COUCHDB_URL || 'http://localhost:5984'; - const couchdbUser = (import.meta as any).env?.VITE_COUCHDB_USER || 'admin'; - const couchdbPassword = - (import.meta as any).env?.VITE_COUCHDB_PASSWORD || 'password'; + const couchdbUrl = process.env.VITE_COUCHDB_URL || 'http://localhost:5984'; + const couchdbUser = process.env.VITE_COUCHDB_USER || 'admin'; + const couchdbPassword = process.env.VITE_COUCHDB_PASSWORD || 'password'; this.baseUrl = couchdbUrl; this.auth = btoa(`${couchdbUser}:${couchdbPassword}`); @@ -72,7 +70,7 @@ export class CouchDBService { throw new Error(`Failed to create database ${dbName}`); } - console.log(`✅ Created CouchDB database: ${dbName}`); + console.warn(`✅ Created CouchDB database: ${dbName}`); } } catch (error) { console.error(`Error checking/creating database ${dbName}:`, error); @@ -83,8 +81,8 @@ export class CouchDBService { private async makeRequest( method: string, path: string, - body?: any - ): Promise { + body?: Record + ): Promise> { const url = `${this.baseUrl}${path}`; const options: RequestInit = { method, @@ -114,7 +112,7 @@ export class CouchDBService { ): Promise { try { const doc = await this.makeRequest('GET', `/${dbName}/${id}`); - return doc; + return doc as T; } catch (error) { if (error instanceof CouchDBError && error.status === 404) { return null; @@ -135,12 +133,15 @@ export class CouchDBService { return { ...doc, _rev: response.rev } as T; } - private async query(dbName: string, selector: any): Promise { + private async query( + dbName: string, + selector: Record + ): Promise { const response = await this.makeRequest('POST', `/${dbName}/_find`, { selector, limit: 1000, }); - return response.docs; + return response.docs as T[]; } // User Management Methods @@ -363,10 +364,13 @@ export class CouchDBService { const settings = await this.getDoc('settings', userId); if (settings) { deletePromises.push( - this.makeRequest('DELETE', `/settings/${userId}?rev=${settings._rev}`) + this.makeRequest( + 'DELETE', + `/settings/${userId}?rev=${settings._rev}` + ).then(() => undefined) ); } - } catch (error) { + } catch (_error) { // Settings might not exist } @@ -377,10 +381,10 @@ export class CouchDBService { this.makeRequest( 'DELETE', `/taken_doses/${userId}?rev=${takenDoses._rev}` - ) + ).then(() => undefined) ); } - } catch (error) { + } catch (_error) { // Taken doses might not exist } diff --git a/services/mailgun.service.ts b/services/mailgun.service.ts index 59a0682..53eb7ac 100644 --- a/services/mailgun.service.ts +++ b/services/mailgun.service.ts @@ -23,14 +23,14 @@ export class MailgunService { // Log configuration status on startup const status = this.getConfigurationStatus(); if (status.mode === 'development') { - console.log( + console.warn( '📧 Mailgun Service: Running in development mode (emails will be logged only)' ); - console.log( + console.warn( '💡 To enable real emails, configure Mailgun credentials in .env.local' ); } else { - console.log( + console.warn( '📧 Mailgun Service: Configured for production with domain:', status.domain ); @@ -45,8 +45,8 @@ export class MailgunService {

Verify Your Email Address

Thank you for signing up for Medication Reminder! Please click the button below to verify your email address:

@@ -57,10 +57,10 @@ export class MailgunService { `, text: ` Verify Your Email - Medication Reminder - + Thank you for signing up! Please verify your email by visiting: ${verificationUrl} - + This link will expire in 24 hours. `, }; @@ -74,8 +74,8 @@ export class MailgunService {

Reset Your Password

You requested to reset your password. Click the button below to set a new password:

@@ -86,10 +86,10 @@ export class MailgunService { `, text: ` Reset Your Password - Medication Reminder - + You requested to reset your password. Visit this link to set a new password: ${resetUrl} - + This link will expire in 1 hour. If you didn't request this, please ignore this email. `, }; @@ -99,7 +99,7 @@ export class MailgunService { try { // In development mode or when Mailgun is not configured, just log the email if (isDevelopmentMode()) { - console.log('📧 Mock Email Sent (Development Mode):', { + console.warn('📧 Mock Email Sent (Development Mode):', { to, subject: template.subject, from: `${this.config.fromName} <${this.config.fromEmail}>`, @@ -140,7 +140,7 @@ export class MailgunService { } const result = await response.json(); - console.log('📧 Email sent successfully via Mailgun:', { + console.warn('📧 Email sent successfully via Mailgun:', { to, subject: template.subject, messageId: result.id, diff --git a/services/oauth.ts b/services/oauth.ts index 770ed47..c2e5b6b 100644 --- a/services/oauth.ts +++ b/services/oauth.ts @@ -1,7 +1,4 @@ import { authService } from './auth/auth.service'; -import { OAuthProvider, OAuthState, User } from '../types'; -import { dbService } from './couchdb.factory'; -import { AccountStatus } from './auth/auth.constants'; // Mock OAuth configuration const GOOGLE_CLIENT_ID = 'mock_google_client_id'; @@ -12,8 +9,6 @@ const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'; const GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize'; // Mock token exchange endpoints -const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token'; -const GITHUB_TOKEN_URL = 'https://github.com/login/oauth/access_token'; // Mock OAuth scopes const GOOGLE_SCOPES = 'openid email profile'; @@ -60,7 +55,7 @@ export const githubAuth = () => { // Mock token exchange const mockExchangeCodeForToken = async ( provider: 'google' | 'github', - code: string + _code: string ): Promise => { // In a real implementation, we would make a POST request to the token endpoint // with the code, client_id, client_secret, and redirect_uri @@ -72,7 +67,7 @@ const mockExchangeCodeForToken = async ( // Mock user info retrieval const mockGetUserInfo = async ( provider: 'google' | 'github', - accessToken: string + _accessToken: string ): Promise<{ email: string; name: string }> => { // In a real implementation, we would make a GET request to the user info endpoint // with the access token diff --git a/vite-env.d.ts b/vite-env.d.ts new file mode 100644 index 0000000..2e6ba28 --- /dev/null +++ b/vite-env.d.ts @@ -0,0 +1,14 @@ +/// + +interface ImportMetaEnv { + readonly VITE_APP_NAME: string; + readonly VITE_COUCHDB_URL: string; + readonly VITE_COUCHDB_USER: string; + readonly VITE_COUCHDB_PASSWORD: string; + readonly VITE_GOOGLE_CLIENT_ID: string; + readonly VITE_GITHUB_CLIENT_ID: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +}