Fix pre-commit script to properly handle multiple files and resolve ESLint warnings

This commit is contained in:
William Valentin
2025-09-07 13:34:39 -07:00
parent 8fa2d3fb60
commit 315303b120
33 changed files with 561 additions and 404 deletions
+13 -12
View File
@@ -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
+13 -13
View File
@@ -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"]
}
+5 -6
View File
@@ -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);
+11 -10
View File
@@ -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:
+119 -1
View File
@@ -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=="],
+1 -1
View File
@@ -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.`);
}
};
+4 -2
View File
@@ -52,8 +52,10 @@ const ChangePasswordModal: React.FC<ChangePasswordModalProps> = ({
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);
}
+3 -2
View File
@@ -46,7 +46,7 @@ const AccountModal: React.FC<AccountModalProps> = ({
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<AccountModalProps> = ({
};
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<AccountModalProps> = ({
setIsDeleting(true);
try {
await onDeleteAllData();
} catch (error) {
} catch {
alert('Failed to delete data.');
} finally {
setIsDeleting(false);
+13 -13
View File
@@ -22,7 +22,7 @@ interface UserContextType {
) => Promise<boolean>;
loginWithOAuth: (
provider: 'google' | 'github',
userData: any
userData: { email: string; username: string; avatar?: string }
) => Promise<boolean>;
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<boolean> => {
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<boolean> => {
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);
+3 -4
View File
@@ -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
+8 -8
View File
@@ -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",
+16 -12
View File
@@ -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
+10 -1
View File
@@ -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',
},
},
];
+1 -1
View File
@@ -8,7 +8,7 @@ function getStoredValue<T>(key: string, defaultValue: T): T {
const saved = localStorage.getItem(key);
try {
return saved ? JSON.parse(saved) : defaultValue;
} catch (e) {
} catch {
return defaultValue;
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MyCustomMeds</title>
<title></title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+3 -1
View File
@@ -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"
+39 -39
View File
@@ -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
+54 -54
View File
@@ -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"
}
+56 -56
View File
@@ -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:"
+34 -34
View File
@@ -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"
+9 -3
View File
@@ -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"
+14 -14
View File
@@ -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) {
+31 -31
View File
@@ -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
}
+34 -34
View File
@@ -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 ""
@@ -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(() => {
@@ -1,5 +1,4 @@
import { EmailVerificationService } from '../emailVerification.service';
import { dbService } from '../../couchdb.factory';
jest.mock('../../couchdb.factory');
jest.mock('../../email');
+1 -1
View File
@@ -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
+15 -11
View File
@@ -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)
+2 -2
View File
@@ -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<string, string> }).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');
+19 -15
View File
@@ -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<any> {
body?: Record<string, unknown>
): Promise<Record<string, unknown>> {
const url = `${this.baseUrl}${path}`;
const options: RequestInit = {
method,
@@ -114,7 +112,7 @@ export class CouchDBService {
): Promise<T | null> {
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<T>(dbName: string, selector: any): Promise<T[]> {
private async query<T>(
dbName: string,
selector: Record<string, unknown>
): Promise<T[]> {
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
}
+13 -13
View File
@@ -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 {
<h2 style="color: #4f46e5;">Verify Your Email Address</h2>
<p>Thank you for signing up for Medication Reminder! Please click the button below to verify your email address:</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${verificationUrl}"
style="background-color: #4f46e5; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
<a href="${verificationUrl}"
style="background-color: #4f46e5; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
Verify Email Address
</a>
</div>
@@ -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 {
<h2 style="color: #4f46e5;">Reset Your Password</h2>
<p>You requested to reset your password. Click the button below to set a new password:</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${resetUrl}"
style="background-color: #4f46e5; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
<a href="${resetUrl}"
style="background-color: #4f46e5; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
Reset Password
</a>
</div>
@@ -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,
+2 -7
View File
@@ -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<string> => {
// 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
+14
View File
@@ -0,0 +1,14 @@
/// <reference types="vite/client" />
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;
}