From efbb699b4b4fb9a47907e6dc7f639e6bbd15a70f Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Sat, 18 Oct 2025 23:36:31 +0200 Subject: [PATCH] Implement environment variable setup for frontend; create .env.production and .env.development files if they don't exist, and update API URLs in frontend components to use dynamic API_BASE_URL for improved configuration management. --- DEPLOYMENT_CHECKLIST.md | 210 ++++++++++++++++++++++++++ deploy.sh | 68 ++++++++- frontend/.env.development | 2 + frontend/.env.production | 3 + frontend/FIX_API_URLS.sh | 76 ++++++++++ frontend/src/components/StatusBox.vue | 6 +- frontend/src/config/api.js | 66 ++++++++ frontend/src/stores/authStore.js | 3 +- frontend/src/stores/timeStore.js | 3 +- frontend/src/views/Calendar.vue | 2 +- frontend/src/views/Export.vue | 4 +- frontend/src/views/Holidays.vue | 8 +- frontend/src/views/Invite.vue | 4 +- frontend/src/views/Login.vue | 2 +- frontend/src/views/PasswordChange.vue | 2 +- frontend/src/views/Permissions.vue | 6 +- frontend/src/views/Profile.vue | 6 +- frontend/src/views/Roles.vue | 4 +- frontend/src/views/Sick.vue | 8 +- frontend/src/views/Timefix.vue | 8 +- frontend/src/views/Timewish.vue | 6 +- frontend/src/views/Vacation.vue | 6 +- frontend/src/views/WeekOverview.vue | 2 +- frontend/src/views/Workdays.vue | 2 +- package-lock.json | 111 ++++++++++++++ 25 files changed, 577 insertions(+), 41 deletions(-) create mode 100644 DEPLOYMENT_CHECKLIST.md create mode 100644 frontend/.env.development create mode 100644 frontend/.env.production create mode 100755 frontend/FIX_API_URLS.sh create mode 100644 frontend/src/config/api.js diff --git a/DEPLOYMENT_CHECKLIST.md b/DEPLOYMENT_CHECKLIST.md new file mode 100644 index 0000000..58c6ac4 --- /dev/null +++ b/DEPLOYMENT_CHECKLIST.md @@ -0,0 +1,210 @@ +# TimeClock v3 - Deployment Checkliste + +Diese Checkliste stellt sicher, dass alle Schritte für ein erfolgreiches Deployment durchgeführt wurden. + +## Pre-Deployment (Lokal) + +- [ ] **Git Status prüfen**: `git status` - Alle Änderungen committed? +- [ ] **Tests durchführen**: Lokal testen mit `npm run dev` +- [ ] **Dependencies aktuell**: `npm install` in backend/ und frontend/ +- [ ] **Frontend-Build testen**: `cd frontend && npm run build` +- [ ] **Keine hardcodierten URLs**: `grep -r "localhost:3010" frontend/src/` + +## Git Push + +```bash +git add . +git commit -m "Deployment vX.X.X" +git push origin main +``` + +## Server-Vorbereitung + +- [ ] **SSH-Zugang**: `ssh tsschulz@tsschulz.de` +- [ ] **Projekt-Verzeichnis**: `cd /var/www/timeclock` +- [ ] **Git Pull**: `git pull origin main` + +## Deployment Durchführen + +### Option 1: Automatisches Deployment (Empfohlen) + +```bash +cd /var/www/timeclock +./deploy.sh update +``` + +Das Script führt automatisch aus: +- ✅ Datenbank-Backup +- ✅ Backend Dependencies installieren +- ✅ Frontend .env.production erstellen +- ✅ Frontend neu bauen (mit korrekter API-URL) +- ✅ Backend neu starten +- ✅ Apache neu laden + +### Option 2: Manuelles Deployment + +```bash +# 1. Backup +./deploy.sh backup + +# 2. Backend +cd /var/www/timeclock/backend +npm install --production +pm2 restart timeclock-backend + +# 3. Frontend +cd /var/www/timeclock/frontend + +# .env.production prüfen/erstellen +cat > .env.production << 'EOF' +VITE_API_URL=/api +EOF + +npm install +rm -rf dist/ +npm run build + +# 4. Apache neu laden +sudo systemctl reload apache2 +``` + +## Post-Deployment Tests + +- [ ] **Backend Health-Check**: + ```bash + curl https://stechuhr3.tsschulz.de/api/health + # Erwartung: {"status":"ok","message":"TimeClock API v3.0.0",...} + ``` + +- [ ] **Frontend lädt**: + ```bash + curl -I https://stechuhr3.tsschulz.de + # Erwartung: HTTP/2 200 + ``` + +- [ ] **Keine localhost URLs**: + ```bash + grep -r "localhost:3010" /var/www/timeclock/frontend/dist/ + # Erwartung: Keine Treffer (oder nur Kommentare) + ``` + +- [ ] **PM2 Status**: + ```bash + pm2 status + # Erwartung: timeclock-backend | online + ``` + +- [ ] **Browser-Tests**: + - [ ] https://stechuhr3.tsschulz.de lädt + - [ ] Login funktioniert + - [ ] API-Calls gehen an `/api` (nicht `localhost:3010`) + - [ ] Keine Console-Errors (F12) + +## Rollback bei Problemen + +```bash +cd /var/www/timeclock +./deploy.sh rollback +``` + +## Monitoring + +```bash +# Backend-Logs live +pm2 logs timeclock-backend + +# Apache-Logs +sudo tail -f /var/log/apache2/stechuhr3-error.log + +# Backend-Status +./deploy.sh status + +# Alle Logs +./deploy.sh logs +``` + +## Häufige Probleme + +### Problem: Frontend zeigt "localhost:3010" Fehler + +**Lösung:** +```bash +cd /var/www/timeclock/frontend +cat .env.production # Sollte VITE_API_URL=/api enthalten +rm -rf dist/ +npm run build +``` + +### Problem: API 502 Bad Gateway + +**Lösung:** +```bash +pm2 restart timeclock-backend +pm2 logs timeclock-backend --lines 50 +``` + +### Problem: Apache zeigt Default-Page + +**Lösung:** +```bash +sudo apache2ctl -S | grep stechuhr3 # Prüfe VirtualHost +sudo systemctl reload apache2 +``` + +### Problem: Browser-Cache + +**Lösung:** +- **Strg + Shift + R** (Hard Reload) +- Oder Private/Incognito Window + +## Deployment-Frequenz + +- **Bugfixes**: Sofort +- **Features**: Nach Testing auf Dev +- **Breaking Changes**: Mit Ankündigung + +## Backup-Strategie + +- **Automatisch**: Täglich um 2 Uhr (via Cronjob) +- **Manuell**: Vor jedem Deployment +- **Retention**: 30 Tage +- **Location**: `/var/backups/timeclock/` + +## Secrets & Credentials + +Niemals committen: +- ❌ `.env` Dateien +- ❌ SSL-Zertifikate +- ❌ Datenbank-Passwörter +- ❌ API-Keys + +Verwende `.gitignore`: +``` +.env +.env.local +.env.production.local +*.key +*.pem +``` + +## Deployment-Log + +Dokumentiere jedes Deployment: + +``` +Datum: 2025-10-18 +Version: v3.0.1 +Änderungen: + - API-URLs von hardcoded auf .env umgestellt + - Apache2-Konfiguration korrigiert + - SSL-Zertifikat eingerichtet +Status: ✅ Erfolgreich +Probleme: Keine +``` + +--- + +**Viel Erfolg beim Deployment! 🚀** + +Bei Fragen: Siehe `DEPLOYMENT.md`, `APACHE2_DEPLOYMENT.md` oder `CHECK_SERVICES.md` + diff --git a/deploy.sh b/deploy.sh index c4d1112..21acb29 100755 --- a/deploy.sh +++ b/deploy.sh @@ -214,6 +214,39 @@ setup_frontend() { print_info "Installiere Frontend-Dependencies..." npm install + # .env.production erstellen falls nicht vorhanden + if [ ! -f ".env.production" ]; then + print_info "Erstelle .env.production..." + cat > .env.production << 'EOF' +# TimeClock v3 - Frontend Production Environment +# API Base URL (relativ, da Apache als Proxy dient) +VITE_API_URL=/api +EOF + print_success ".env.production erstellt" + else + print_success ".env.production bereits vorhanden" + fi + + # .env.development erstellen falls nicht vorhanden (für lokale Entwicklung) + if [ ! -f ".env.development" ]; then + print_info "Erstelle .env.development..." + cat > .env.development << 'EOF' +# TimeClock v3 - Frontend Development Environment +VITE_API_URL=http://localhost:3010/api +EOF + print_success ".env.development erstellt" + fi + + # Prüfe ob api.js existiert + if [ ! -f "src/config/api.js" ]; then + print_warning "src/config/api.js fehlt! Bitte git pull ausführen." + exit 1 + fi + + # Alte dist/ löschen für sauberen Build + print_info "Lösche alten Build..." + rm -rf dist/ + print_info "Erstelle Produktions-Build..." npm run build @@ -222,6 +255,14 @@ setup_frontend() { exit 1 fi + # Prüfe ob localhost:3010 noch im Build ist + if grep -r "localhost:3010" dist/ > /dev/null 2>&1; then + print_warning "⚠️ localhost:3010 noch im Build gefunden - eventuell falsches .env verwendet" + print_info "Prüfe .env.production: $(cat .env.production 2>/dev/null || echo 'nicht gefunden')" + else + print_success "✅ Build verwendet korrekte API-URL" + fi + print_success "Frontend Build erstellt!" } @@ -390,15 +431,40 @@ do_update() { cd $BACKEND_DIR npm install --production - # Frontend neu bauen + # Frontend aktualisieren print_info "Aktualisiere Frontend..." cd $FRONTEND_DIR + + # .env.production prüfen/erstellen + if [ ! -f ".env.production" ]; then + print_info "Erstelle .env.production..." + cat > .env.production << 'EOF' +# TimeClock v3 - Frontend Production Environment +VITE_API_URL=/api +EOF + fi + npm install + + # Sauberer Build + rm -rf dist/ npm run build + # Prüfe Build + if grep -r "localhost:3010" dist/ > /dev/null 2>&1; then + print_warning "⚠️ localhost:3010 noch im Build - bitte prüfen" + fi + # Service neu starten restart_backend + # Apache Cache leeren + if [ "$WEBSERVER" = "apache2" ]; then + sudo systemctl reload apache2 + else + sudo systemctl reload nginx + fi + print_success "Update abgeschlossen!" } diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 0000000..a66eceb --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1,2 @@ +# TimeClock v3 - Frontend Development Environment +VITE_API_URL=http://localhost:3010/api diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 0000000..1a09bce --- /dev/null +++ b/frontend/.env.production @@ -0,0 +1,3 @@ +# TimeClock v3 - Frontend Production Environment +# API Base URL (relativ, da Apache als Proxy dient) +VITE_API_URL=/api diff --git a/frontend/FIX_API_URLS.sh b/frontend/FIX_API_URLS.sh new file mode 100755 index 0000000..3018594 --- /dev/null +++ b/frontend/FIX_API_URLS.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +# ============================================================================= +# TimeClock v3 - Fix hardcodierte API URLs +# ============================================================================= + +set -e + +echo "🔧 Ersetze hardcodierte API-URLs durch zentrale Konfiguration..." + +cd "$(dirname "$0")" + +# Backup erstellen +echo "📦 Erstelle Backup..." +tar -czf ~/timeclock-frontend-backup-$(date +%Y%m%d_%H%M%S).tar.gz src/ + +# .env.production erstellen +echo "📝 Erstelle .env.production..." +cat > .env.production << 'EOF' +# TimeClock v3 - Frontend Production Environment +# API Base URL (relativ, da Apache als Proxy dient) +VITE_API_URL=/api +EOF + +# .env.development erstellen +echo "📝 Erstelle .env.development..." +cat > .env.development << 'EOF' +# TimeClock v3 - Frontend Development Environment +VITE_API_URL=http://localhost:3010/api +EOF + +# Ersetze alle hardcodierten URLs in Views +echo "🔄 Ersetze URLs in Views..." + +# Pattern: http://localhost:3010/api → ${API_URL} +# Muss in mehreren Schritten erfolgen, da die Syntax unterschiedlich ist + +find src/views -name "*.vue" -type f -exec sed -i "s|'http://localhost:3010/api|'\${API_URL}|g" {} \; +find src/views -name "*.vue" -type f -exec sed -i 's|"http://localhost:3010/api|"${API_URL}|g' {} \; +find src/views -name "*.vue" -type f -exec sed -i 's|`http://localhost:3010/api|`${API_URL}|g' {} \; + +# Füge API_URL Import hinzu wo benötigt +echo "📦 Füge Imports hinzu..." + +# Einfacher Ansatz: Erstelle eine Liste der Dateien mit API-Aufrufen +grep -l "localhost:3010" src/views/*.vue 2>/dev/null | while read -r file; do + # Prüfe ob Import bereits vorhanden + if ! grep -q "import.*API_BASE_URL.*from.*@/config/api" "$file"; then + # Füge Import nach dem ersten diff --git a/frontend/src/views/PasswordChange.vue b/frontend/src/views/PasswordChange.vue index ae48341..e82d105 100644 --- a/frontend/src/views/PasswordChange.vue +++ b/frontend/src/views/PasswordChange.vue @@ -92,7 +92,7 @@ async function changePassword() { try { loading.value = true - const response = await fetch('http://localhost:3010/api/password', { + const response = await fetch('${API_URL}/password', { method: 'PUT', headers: { 'Content-Type': 'application/json', diff --git a/frontend/src/views/Permissions.vue b/frontend/src/views/Permissions.vue index e7659da..03daf84 100644 --- a/frontend/src/views/Permissions.vue +++ b/frontend/src/views/Permissions.vue @@ -102,7 +102,7 @@ const form = ref({ // Lade alle Watcher async function loadWatchers() { try { - const response = await fetch('http://localhost:3010/api/watcher', { + const response = await fetch('${API_URL}/watcher', { headers: { 'Authorization': `Bearer ${authStore.token}` } @@ -128,7 +128,7 @@ async function addWatcher() { try { loading.value = true - const response = await fetch('http://localhost:3010/api/watcher', { + const response = await fetch('${API_URL}/watcher', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -172,7 +172,7 @@ async function removeWatcher(id, email) { try { loading.value = true - const response = await fetch(`http://localhost:3010/api/watcher/${id}`, { + const response = await fetch(`${API_URL}/watcher/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${authStore.token}` diff --git a/frontend/src/views/Profile.vue b/frontend/src/views/Profile.vue index fe623d2..c830f01 100644 --- a/frontend/src/views/Profile.vue +++ b/frontend/src/views/Profile.vue @@ -135,7 +135,7 @@ const form = ref({ async function loadProfile() { try { loading.value = true - const response = await fetch('http://localhost:3010/api/profile', { + const response = await fetch('${API_URL}/profile', { headers: { 'Authorization': `Bearer ${authStore.token}` } @@ -165,7 +165,7 @@ async function loadProfile() { // Lade Bundesländer async function loadStates() { try { - const response = await fetch('http://localhost:3010/api/profile/states', { + const response = await fetch('${API_URL}/profile/states', { headers: { 'Authorization': `Bearer ${authStore.token}` } @@ -185,7 +185,7 @@ async function loadStates() { async function saveProfile() { try { loading.value = true - const response = await fetch('http://localhost:3010/api/profile', { + const response = await fetch('${API_URL}/profile', { method: 'PUT', headers: { 'Content-Type': 'application/json', diff --git a/frontend/src/views/Roles.vue b/frontend/src/views/Roles.vue index 88f0afc..ecef2e0 100644 --- a/frontend/src/views/Roles.vue +++ b/frontend/src/views/Roles.vue @@ -82,7 +82,7 @@ const { showModal, modalConfig, alert, confirm, onConfirm, onCancel } = useModal async function loadUsers() { try { loading.value = true - const response = await fetch('http://localhost:3010/api/roles/users', { + const response = await fetch('${API_URL}/roles/users', { headers: { 'Authorization': `Bearer ${authStore.token}` } @@ -133,7 +133,7 @@ async function demoteToUser(userId, userName) { async function updateRole(userId, newRole) { try { loading.value = true - const response = await fetch(`http://localhost:3010/api/roles/users/${userId}`, { + const response = await fetch(`${API_URL}/roles/users/${userId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', diff --git a/frontend/src/views/Sick.vue b/frontend/src/views/Sick.vue index 39d23dd..d3d23f9 100644 --- a/frontend/src/views/Sick.vue +++ b/frontend/src/views/Sick.vue @@ -123,7 +123,7 @@ const form = ref({ async function loadSickEntries() { try { loading.value = true - const response = await fetch('http://localhost:3010/api/sick', { + const response = await fetch('${API_URL}/sick', { headers: { 'Authorization': `Bearer ${authStore.token}` } @@ -145,7 +145,7 @@ async function loadSickEntries() { // Lade alle Krankheitstypen async function loadSickTypes() { try { - const response = await fetch('http://localhost:3010/api/sick/types', { + const response = await fetch('${API_URL}/sick/types', { headers: { 'Authorization': `Bearer ${authStore.token}` } @@ -171,7 +171,7 @@ async function createSick() { try { loading.value = true - const response = await fetch('http://localhost:3010/api/sick', { + const response = await fetch('${API_URL}/sick', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -212,7 +212,7 @@ async function deleteSick(id) { try { loading.value = true - const response = await fetch(`http://localhost:3010/api/sick/${id}`, { + const response = await fetch(`${API_URL}/sick/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${authStore.token}` diff --git a/frontend/src/views/Timefix.vue b/frontend/src/views/Timefix.vue index b4b7a3b..1941a88 100644 --- a/frontend/src/views/Timefix.vue +++ b/frontend/src/views/Timefix.vue @@ -179,7 +179,7 @@ async function loadWorklogEntries() { if (!date) return // Hole alle Worklog-Einträge für das Datum vom Backend - const response = await fetch(`http://localhost:3010/api/timefix/worklog-entries?date=${date}`, { + const response = await fetch(`${API_URL}/timefix/worklog-entries?date=${date}`, { headers: authStore.getAuthHeaders() }) @@ -224,7 +224,7 @@ function onEntrySelected() { async function loadTimefixes() { try { loading.value = true - const response = await fetch('http://localhost:3010/api/timefix', { + const response = await fetch('${API_URL}/timefix', { headers: authStore.getAuthHeaders() }) @@ -248,7 +248,7 @@ async function createTimefix() { try { loading.value = true - const response = await fetch('http://localhost:3010/api/timefix', { + const response = await fetch('${API_URL}/timefix', { method: 'POST', headers: { ...authStore.getAuthHeaders(), @@ -290,7 +290,7 @@ async function deleteTimefix(id) { try { loading.value = true - const response = await fetch(`http://localhost:3010/api/timefix/${id}`, { + const response = await fetch(`${API_URL}/timefix/${id}`, { method: 'DELETE', headers: authStore.getAuthHeaders() }) diff --git a/frontend/src/views/Timewish.vue b/frontend/src/views/Timewish.vue index 3a6ed7a..03865dd 100644 --- a/frontend/src/views/Timewish.vue +++ b/frontend/src/views/Timewish.vue @@ -177,7 +177,7 @@ function onWishtypeChange() { async function loadTimewishes() { try { loading.value = true - const response = await fetch('http://localhost:3010/api/timewish', { + const response = await fetch('${API_URL}/timewish', { headers: { 'Authorization': `Bearer ${authStore.token}` } @@ -205,7 +205,7 @@ async function createTimewish() { try { loading.value = true - const response = await fetch('http://localhost:3010/api/timewish', { + const response = await fetch('${API_URL}/timewish', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -248,7 +248,7 @@ async function deleteTimewish(id) { try { loading.value = true - const response = await fetch(`http://localhost:3010/api/timewish/${id}`, { + const response = await fetch(`${API_URL}/timewish/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${authStore.token}` diff --git a/frontend/src/views/Vacation.vue b/frontend/src/views/Vacation.vue index 6fc1e7c..a7f96fd 100644 --- a/frontend/src/views/Vacation.vue +++ b/frontend/src/views/Vacation.vue @@ -135,7 +135,7 @@ function onStartDateChange() { async function loadVacations() { try { loading.value = true - const response = await fetch('http://localhost:3010/api/vacation', { + const response = await fetch('${API_URL}/vacation', { headers: authStore.getAuthHeaders() }) @@ -159,7 +159,7 @@ async function createVacation() { try { loading.value = true - const response = await fetch('http://localhost:3010/api/vacation', { + const response = await fetch('${API_URL}/vacation', { method: 'POST', headers: { ...authStore.getAuthHeaders(), @@ -197,7 +197,7 @@ async function deleteVacation(id) { try { loading.value = true - const response = await fetch(`http://localhost:3010/api/vacation/${id}`, { + const response = await fetch(`${API_URL}/vacation/${id}`, { method: 'DELETE', headers: authStore.getAuthHeaders() }) diff --git a/frontend/src/views/WeekOverview.vue b/frontend/src/views/WeekOverview.vue index 938f0be..0a95c25 100644 --- a/frontend/src/views/WeekOverview.vue +++ b/frontend/src/views/WeekOverview.vue @@ -233,7 +233,7 @@ const loadWeekData = async () => { try { loading.value = true - const response = await fetch(`http://localhost:3010/api/week-overview?weekOffset=${weekOffset.value}`, { + const response = await fetch(`${API_URL}/week-overview?weekOffset=${weekOffset.value}`, { headers: { 'Authorization': `Bearer ${authStore.token}`, 'Content-Type': 'application/json' diff --git a/frontend/src/views/Workdays.vue b/frontend/src/views/Workdays.vue index 7336a85..8bf91bd 100644 --- a/frontend/src/views/Workdays.vue +++ b/frontend/src/views/Workdays.vue @@ -61,7 +61,7 @@ async function loadStatistics() { loading.value = true error.value = null - const response = await fetch(`http://localhost:3010/api/workdays?year=${selectedYear.value}`, { + const response = await fetch(`${API_URL}/workdays?year=${selectedYear.value}`, { headers: { 'Authorization': `Bearer ${authStore.token}` } diff --git a/package-lock.json b/package-lock.json index a6453fe..362e628 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^4.5.2", + "terser": "^5.27.0", "vite": "^5.0.8" } }, @@ -542,12 +543,55 @@ "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", "license": "MIT" }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", @@ -1052,6 +1096,19 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1441,6 +1498,13 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/buffer-indexof-polyfill": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", @@ -1616,6 +1680,13 @@ "color-support": "bin.js" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/compress-commons": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", @@ -4207,6 +4278,16 @@ "node": ">=10" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4216,6 +4297,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/spawn-command": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", @@ -4324,6 +4416,25 @@ "node": ">=6" } }, + "node_modules/terser": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", + "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/timeclock-backend": { "resolved": "backend", "link": true