feat: add Docker image, production server and health check

- Multi-stage Dockerfile on node:24-slim, non-root, healthcheck, /app/data volume
- server.mjs wraps the Astro handler with brotli/gzip compression,
  security headers and graceful SIGTERM shutdown (npm start)
- GET /api/health/ reports server and view-counter DB status
- Move build-only packages to devDependencies (smaller runtime image)
- docker-compose.yml as an alternative for Dokploy Compose

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 02:58:45 +03:00
parent 7d8fcce2e1
commit 91cb337c03
10 changed files with 446 additions and 28 deletions
+15
View File
@@ -0,0 +1,15 @@
node_modules
dist
.astro
data
.git
.gitignore
.env*
.claude
.vscode
docs
*.md
!src/**/*.md
!src/**/*.mdx
Dockerfile
docker-compose.yml
+30
View File
@@ -0,0 +1,30 @@
# ta1sph.info — Astro static site + small Node server for /api/* (view counter).
# Dokploy: Build Type "Dockerfile", container port 4321, volume at /app/data.
# ---- build ----
FROM node:24-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev
# ---- runtime ----
FROM node:24-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production \
HOST=0.0.0.0 \
PORT=4321 \
DATA_DIR=/app/data
COPY --from=build --chown=node:node /app/package.json ./
COPY --from=build --chown=node:node /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/dist ./dist
COPY --from=build --chown=node:node /app/server.mjs ./
# Named volume mounted here keeps the view-counter database across deploys.
RUN mkdir -p /app/data && chown node:node /app/data
VOLUME ["/app/data"]
USER node
EXPOSE 4321
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD node -e "fetch('http://127.0.0.1:4321/api/health/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
CMD ["node", "server.mjs"]
+14 -7
View File
@@ -1,21 +1,21 @@
# PROJECT_MEMORY — ta1sph.info
> Claude'un bu proje için kalıcı hafızası. Her görev sonunda güncellenir. Kısa ve güncel tutulur.
> Son güncelleme: 2026-10-16 · Güncelleyen: Claude (Siteyi Kur — Aşama 7)
> Son güncelleme: 2026-10-16 · Güncelleyen: Claude (Siteyi Kur — Aşama 8)
## 1. Durum
- **Aşama:** 7 tamamlandı (kalite). Sıradaki: **Aşama 8 — Deploy hazırlığı**.
- **Tamamlanan:** Aşama 0–7. Stil önizleme yalnızca dev'de: `http://localhost:4321/styleguide/`. Lighthouse (mobil, 3 koşu ortancası, yerel): A11y/BP/SEO 100 her sayfada; Perf temiz koşullarda 98–100, LCP ~1,4–1,9 sn. Yayında PageSpeed Insights ile doğrulanmalı.
- **Açık işler:** Aşama 8. Node sunucusu yanıtları sıkıştırmıyor → A8'de Traefik `compress` middleware veya eşdeğeri şart. 3 örnek yazı (`örnek` etiketli) kullanıcı isteyince silinecek. Hakkımda tanıtım paragrafı kullanıcı metin verirse.
- **Aşama:** 8 kod tarafı tamamlandı (Docker + sunucu). Sıradaki: **Dokploy kurulumu kullanıcıyla adım adım** (bkz. `references/deployment.md`), sonra `Yayına Al`.
- **Tamamlanan:** Aşama 0–8 (kod). Docker imajı yerelde build + çalıştırma + volume kalıcılığı test edildi (test kalıntıları silindi). Lighthouse (yerel): A11y/BP/SEO 100; Perf temiz koşullarda 98–100. Yayında PageSpeed Insights ile doğrulanmalı.
- **Açık işler:** Dokploy: DNS, port yönlendirme, Application, volume, domain + Let's Encrypt, www→apex, auto deploy, Search Console. 3 örnek yazı (`örnek` etiketli) kullanıcı isteyince silinecek. Hakkımda tanıtım paragrafı kullanıcı metin verirse.
- **Bilinen sorunlar:** Arama (Pagefind) `astro dev`'de çalışmaz, yalnızca build sonrası. Kullanıcının elle başlattığı dev sunucusu yeni koleksiyonu görmüyorsa yeniden başlatılmalı. `npm audit`: 2 orta seviye (postcss-selector-parser 6.0.10, @tailwindcss/typography sabitliyor) — yalnızca build zamanı, düzeltme typography'yi eski sürüme düşürdüğü için uygulanmadı.
## 2. Ortam
- Astro 7.4.0 · TailwindCSS 4.3.3 (`@tailwindcss/vite`) · TypeScript 6 strict · zod = `astro/zod` (ayrı paket yok)
- @astrojs/node 11.1.8 · mdx 8.1.0 · sitemap 3.7.4 · rss 4.0.19 · astro-icon 1.2.0 · pagefind 1.5.2
- Yerel: Node v26.11.1, npm 12.2.0, Docker 29.8.1 · `package.json` engines: node >=22.22
- Repo: yerel git (Gitea remote henüz yok; push yalnızca kullanıcı onayıyla) · Branch: `main`
- Yerel: Node v26.11.1, npm 12.2.0, Docker 29.8.1 · `package.json` engines: node >=24 · Docker: `node:24-slim`
- Repo: `origin` = https://gitea.ta1sph.info/suphicakir/ta1sph.info.git · Branch: `main` · **Push'u kullanıcı yapar** (Claude push etmez)
- Canlı: https://ta1sph.info (Dokploy, HomeServer Atlas) — _henüz yayında değil_
## 3. Dosya Haritası
@@ -48,10 +48,13 @@
- `public/fonts/*.woff2` + `src/styles/fonts.css` — **üretilir:** `npm run fonts` (`scripts/subset-fonts.mjs`, subset-font; Latin+Türkçe+sembol, wght Rubik 400–900 / Inter 400–700; 188 KB → 66 KB). Kaynak `@fontsource-variable/*` (devDeps). BaseLayout iki latin dosyasını preload eder
- `public/favicon.{svg,ico}`, `apple-touch-icon.png`, `icon-192/512.png`, `icon-maskable-512.png` — **üretilir:** `npm run icons` (`scripts/generate-icons.mjs`; kırmızı zemin + siyah kule, 16px için kalın varyant)
- `scripts/check-site.mjs` — `npm run check:site` (build sonrası): tek h1, alt, title/description/canonical, tekrar eden id, iç link + anchor
- `server.mjs` — üretim girişi (`npm start`): `ASTRO_NODE_AUTOSTART=disabled` ile Astro handler'ını sarar; `compression` (br/gzip), güvenlik başlıkları, SIGTERM'de düzgün kapanma. `/_astro/*` immutable cache adapter'dan
- `src/pages/api/health.ts` — `GET /api/health/` (trailing slash!) → `{status, db}`; DB yoksa 503
- `Dockerfile` (node:24-slim, build → `npm prune --omit=dev` → runtime; USER node; HEALTHCHECK; VOLUME /app/data) · `.dockerignore` · `docker-compose.yml` (Dokploy Compose alternatifi)
- Bağımlılıklar: runtime yalnızca `astro`, `@astrojs/node`, `compression`; geri kalan her şey devDependencies (imaj 531 MB, ~200 MB'ı node:24-slim)
- `src/dev/Styleguide.astro` — `astro.config.mjs`'deki `devRoutes` entegrasyonu ile sadece `astro dev`'de `/styleguide/` (`_` önekli sayfalar Astro'da rota olmaz)
- `src/lib/slug.ts` — Türkçe karakter dönüştürücü `slugify`
- `scripts/new-post.mjs` — `npm run new:post -- "Başlık"` → `src/content/blog/<slug>/index.md` (draft: true); `slug.ts`'yi Node type-stripping ile import eder
- `src/pages/index.astro` — geçici yer tutucu (Aşama 3'te değişecek)
- `docs/design-reference/` — Huemint Magazine ekran görüntüleri · `CLAUDE.md` → `AGENTS.md` + bu dosya
- Build: `astro build && pagefind --site dist/client` · statik çıktı `dist/client`, sunucu `dist/server/entry.mjs`
@@ -68,6 +71,9 @@
| 2026-10-15 | Kanonik alan: apex `ta1sph.info`, www → 301 | SEO tekilleştirme |
| 2026-10-15 | Fontlar self-host (Fontsource: Rubik + Inter) | Performans + gizlilik |
| 2026-10-16 | Ek token'lar: `accent-fill`/`on-accent-fill`, `accent-text` (khaki'de fg'ye döner) | Ölçüm: ink/red 4.46, red-strong/khaki 3.85 → küçük metin AA'yı geçmiyordu. Kırmızı zeminli butonlar ≥19px bold |
| 2026-10-16 | Sıkıştırma konteyner içinde (`server.mjs` + `compression`), Traefik ayarı yok | Kullanıcının Dokploy'da ek middleware kurmasına gerek kalmaz |
| 2026-10-16 | Docker taban imajı `node:24-slim` (LTS) | `node:sqlite` 24 ve 26'da uyarısız test edildi; 24 LTS Nisan 2028'e kadar destekli |
| 2026-10-16 | Kalıcı veri için Dokploy'da named volume (bind mount değil) | Named volume ilk mount'ta imajdaki `node` sahipliğini alır; bind mount root olur → yazma hatası |
| 2026-10-16 | Yazı başına OG görseli build'de (Satori) — kullanıcı kararı bana bıraktı | Paylaşımlarda başlıklı kart; harici servis yok |
| 2026-10-16 | Favicon = kırmızı kare + siyah anten kulesi (Tabler) | "TA1" 16px'te okunmuyor; vurgu rengi + İletişim ikonuyla tutarlı |
| 2026-10-16 | Fontlar alt kümelenip `public/fonts`'tan; `build.inlineStylesheets: 'always'` | LCP'yi geciktiren render-blocking CSS + 190 KB font; ölçümde FCP 2,3 → ~1 sn |
@@ -100,6 +106,7 @@
## 6. Değişiklik Günlüğü (en yeni üstte)
- 2026-10-16 | Siteyi Kur A8 | Dockerfile, .dockerignore, compose, server.mjs (sıkıştırma + güvenlik başlıkları), /api/health, build-only bağımlılıklar devDeps'e; Docker'da uçtan uca test | `Dockerfile`, `.dockerignore`, `docker-compose.yml`, `server.mjs`, `src/pages/api/health.ts`, `src/lib/views.ts`, `package.json`, `README.md`
- 2026-10-16 | Siteyi Kur A7 | OG görselleri (yazı başına + varsayılan), favicon seti + manifest, robots.txt, font alt kümeleme + preload, CSS inline, site denetim script'i, Lighthouse | `src/lib/og.ts`, `src/pages/{og,og-default.png,robots.txt,site.webmanifest}*`, `scripts/{generate-icons,subset-fonts,check-site}.mjs`, `public/`, `src/styles/fonts.css`, `Seo.astro`, `BaseLayout.astro`, `astro.config.mjs`
- 2026-10-16 | Siteyi Kur A6 | Hakkımda, İletişim (Leaflet harita, gizlenmiş e-posta), Gizlilik/Lisans/Sorumluluk Reddi, 404, LICENSE dosyaları; PageHeader + CodeCopy ortak bileşenleri; axe temiz | `src/pages/{hakkimda,iletisim,[legal],404}.astro`, `src/content/legal/`, `src/data/{about,contact,pages}.json`, `src/components/{PageHeader,CodeCopy,Card}.astro`, `LICENSE*`, `global.css`
- 2026-10-16 | Siteyi Kur A5 | Görüntülenme sayacı (node:sqlite, anonim, 24 sa tekil, bot filtresi), top API, sidebar öne çıkanlar API + yedek; uçtan uca test | `src/lib/views.ts`, `src/pages/api/views/`, `src/components/blog/Sidebar.astro`, `src/pages/blog/[slug].astro`, `src/lib/posts.ts`
+8
View File
@@ -18,6 +18,14 @@ Suphi Çakır (TA1SPH) — amatör telsizcilik blog & portfolyo sitesi.
| `npm run format` | Prettier ile biçimlendirir |
| `npm run new:post -- "Başlık"` | `src/content/blog/<slug>/index.md` taslağı oluşturur |
## Yayın (Docker · Dokploy)
- `Dockerfile` (Node 24 LTS, çok aşamalı) → konteyner `node server.mjs`, port **4321**, kullanıcı `node`.
- Kalıcı veri: görüntülenme sayacı `/app/data/views.db` → Dokploy'da **Volume Mount** `/app/data`.
- Sağlık kontrolü: `GET /api/health/` (`{"status":"ok","db":true}`).
- Ortam değişkenleri: `HOST` (0.0.0.0), `PORT` (4321), `DATA_DIR` (/app/data) — imajda varsayılan.
- `docker-compose.yml`: Dokploy "Compose" türü için alternatif.
## Lisans
Kod [MIT](LICENSE), içerik [CC BY-SA 4.0](LICENSE-CONTENT).
+16
View File
@@ -0,0 +1,16 @@
# Alternative to Dokploy's "Application" type: Dokploy → Create Service → Compose.
# Domains/HTTPS are still configured in Dokploy's UI (Traefik); no host ports are published.
services:
web:
build: .
restart: unless-stopped
environment:
NODE_ENV: production
DATA_DIR: /app/data
volumes:
- ta1sph-data:/app/data
expose:
- '4321'
volumes:
ta1sph-data:
+292 -10
View File
@@ -8,26 +8,27 @@
"name": "ta1sph.info",
"version": "0.0.1",
"dependencies": {
"@astrojs/markdown-satteri": "^0.4.4",
"@astrojs/mdx": "^8.1.0",
"@astrojs/node": "^11.1.8",
"@astrojs/rss": "^4.0.19",
"@astrojs/sitemap": "^3.7.4",
"@iconify-json/tabler": "^1.2.42",
"@tailwindcss/vite": "^4.3.3",
"astro": "^7.4.0",
"astro-icon": "^1.2.0",
"leaflet": "^1.9.4",
"tailwindcss": "^4.3.3"
"compression": "^1.8.2"
},
"devDependencies": {
"@astrojs/check": "^0.9.10",
"@astrojs/markdown-satteri": "^0.4.4",
"@astrojs/mdx": "^8.1.0",
"@astrojs/rss": "^4.0.19",
"@astrojs/sitemap": "^3.7.4",
"@fontsource-variable/inter": "^5.3.0",
"@fontsource-variable/rubik": "^5.3.0",
"@fontsource/inter": "^5.3.0",
"@fontsource/rubik": "^5.3.0",
"@iconify-json/tabler": "^1.2.42",
"@tailwindcss/typography": "^0.5.20",
"@tailwindcss/vite": "^4.3.3",
"@types/compression": "^1.8.1",
"@types/leaflet": "^1.9.22",
"astro-icon": "^1.2.0",
"leaflet": "^1.9.4",
"pagefind": "^1.5.2",
"prettier": "^3.9.10",
"prettier-plugin-astro": "^1.1.1",
@@ -35,16 +36,18 @@
"satori": "^0.60.0",
"sharp": "^0.35.5",
"subset-font": "^2.9.0",
"tailwindcss": "^4.3.3",
"typescript": "^6.0.3"
},
"engines": {
"node": ">=22.22.0"
"node": ">=24"
}
},
"node_modules/@antfu/install-pkg": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-2.1.0.tgz",
"integrity": "sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"package-manager-detector": "^1.8.0",
@@ -418,6 +421,7 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-8.1.0.tgz",
"integrity": "sha512-xzrlXrjpBYvC27MTkP+2NMiJG4Jjl2DcoNc/EF9RNxAp0dwhvXC3b22+7OO4kHJQKfISk2uy38HYp1UxX7JfSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@astrojs/internal-helpers": "0.13.0",
@@ -468,6 +472,7 @@
"version": "4.0.19",
"resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-4.0.19.tgz",
"integrity": "sha512-e+z5wYeYtffQdHQO8c2tkSd2JEBdAuRXJV4ZEU5IxkYeE6e39woDd7nw1PH1Kk2tEYNCYuKdylnnbhGmt61awA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-xml-parser": "^5.5.7",
@@ -479,6 +484,7 @@
"version": "3.7.4",
"resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.4.tgz",
"integrity": "sha512-LbKNC24bdUWcQf/pThB6qLlSqHojxGjZDURIzFocY8rlWnAn2t74nnhnK6S5x0NHriHoAduLEpVjRykmeGiVvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"sitemap": "^9.0.0",
@@ -765,6 +771,7 @@
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@cyberalien/svg-utils/-/svg-utils-1.2.19.tgz",
"integrity": "sha512-paDJoDu+LhuH5Ma1q0BDqBpg6d16Xk22cZZrlg/s/J9oB8KgnnpU/snaEETJu2G1pwYcWQINUz6WUqu0Wm9k6A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@iconify/types": "^2.0.0"
@@ -907,6 +914,7 @@
"version": "1.2.42",
"resolved": "https://registry.npmjs.org/@iconify-json/tabler/-/tabler-1.2.42.tgz",
"integrity": "sha512-fObaBh/H2zJphk3lS1dWPUf/buuGaCHQXnB4UYgKOPXQub5Ac1qEu0AHRj/0+E2FEl8pWfgM2xQWzfz7ffcY+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@iconify/types": "*"
@@ -916,6 +924,7 @@
"version": "5.0.14",
"resolved": "https://registry.npmjs.org/@iconify/tools/-/tools-5.0.14.tgz",
"integrity": "sha512-wJ4ZE3/HZh2iBqLwG+8FOMHe7CMJAJqwSXZSpPwMdj1gZM3Z4t2hbQMVw4Vm45oQ985qn0g89PUIcDEdKTheBA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cyberalien/svg-utils": "^1.2.19",
@@ -931,12 +940,14 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
"dev": true,
"license": "MIT"
},
"node_modules/@iconify/utils": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.7.tgz",
"integrity": "sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@antfu/install-pkg": "^2.0.1",
@@ -1497,6 +1508,7 @@
"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",
@@ -1507,6 +1519,7 @@
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -1517,6 +1530,7 @@
"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"
@@ -1532,6 +1546,7 @@
"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",
@@ -1563,6 +1578,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.1.0.tgz",
"integrity": "sha512-LsS/DjHr+uDM647Gru/cA8+J3a3HfhttwCKLyuoyN7yXTFCxKBtSgMQBvrW9yNPa2/zDRUNuGPaH5QUFoa4arQ==",
"dev": true,
"funding": [
{
"type": "github",
@@ -2083,6 +2099,7 @@
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
"integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.5",
@@ -2098,6 +2115,7 @@
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
@@ -2130,6 +2148,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2150,6 +2169,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2170,6 +2190,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2190,6 +2211,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2210,6 +2232,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2230,6 +2253,7 @@
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
@@ -2253,6 +2277,7 @@
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
@@ -2276,6 +2301,7 @@
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
@@ -2299,6 +2325,7 @@
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
@@ -2322,6 +2349,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2342,6 +2370,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2359,6 +2388,7 @@
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
@@ -2368,6 +2398,7 @@
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
"integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 20"
@@ -2394,6 +2425,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2410,6 +2442,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2426,6 +2459,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2442,6 +2476,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2458,6 +2493,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2474,6 +2510,7 @@
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
@@ -2493,6 +2530,7 @@
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
@@ -2512,6 +2550,7 @@
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
@@ -2531,6 +2570,7 @@
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
@@ -2558,6 +2598,7 @@
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -2579,6 +2620,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2595,6 +2637,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2621,6 +2664,7 @@
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz",
"integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@tailwindcss/node": "4.3.3",
@@ -2641,6 +2685,38 @@
"tslib": "^2.4.0"
}
},
"node_modules/@types/body-parser": {
"version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
}
},
"node_modules/@types/compression": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/express": "*",
"@types/node": "*"
}
},
"node_modules/@types/connect": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
@@ -2656,6 +2732,31 @@
"@types/estree": "*"
}
},
"node_modules/@types/express": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^5.0.0",
"@types/serve-static": "^2"
}
},
"node_modules/@types/express-serve-static-core": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz",
"integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*",
"@types/send": "*"
}
},
"node_modules/@types/geojson": {
"version": "7946.0.16",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
@@ -2672,6 +2773,13 @@
"@types/unist": "*"
}
},
"node_modules/@types/http-errors": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/leaflet": {
"version": "1.9.22",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.22.tgz",
@@ -2704,20 +2812,57 @@
"version": "24.19.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.2.tgz",
"integrity": "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": ">=7.24.0 <7.24.7"
}
},
"node_modules/@types/qs": {
"version": "6.15.1",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz",
"integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/sax": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
"integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/send": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
"integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/serve-static": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
"integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/http-errors": "*",
"@types/node": "*"
}
},
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
@@ -2944,6 +3089,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz",
"integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==",
"dev": true,
"funding": [
{
"type": "github",
@@ -2956,6 +3102,7 @@
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true,
"license": "MIT"
},
"node_modules/argparse": {
@@ -3061,6 +3208,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/astro-icon/-/astro-icon-1.2.0.tgz",
"integrity": "sha512-0848V2WZuTPhqGSqLeZimLJZ5wmamMtWYzvA2kvg8s2DrUPZ3/nrTcHJWN0eJHt62jy7uvNxgTNiUJGIr7JNxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@iconify/tools": "^5.0.12",
@@ -3106,6 +3254,15 @@
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC"
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/camelize": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
@@ -3253,6 +3410,56 @@
"node": ">= 18"
}
},
"node_modules/compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
"integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
"license": "MIT",
"dependencies": {
"mime-db": ">= 1.43.0 < 2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/compression": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.2.tgz",
"integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"destroy": "1.2.0",
"negotiator": "~0.6.4",
"on-headers": "~1.1.0",
"safe-buffer": "5.2.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.8.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/compression/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/compression/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/cookie": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz",
@@ -3461,6 +3668,16 @@
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
"license": "MIT"
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"license": "MIT",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -3618,6 +3835,7 @@
"version": "5.26.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz",
"integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -3734,6 +3952,7 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz",
"integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==",
"dev": true,
"funding": [
{
"type": "github",
@@ -3750,6 +3969,7 @@
"version": "5.11.2",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.2.tgz",
"integrity": "sha512-R9iDuNrQYeQut46cn2r2wHKn4HYzVDvDm5J1wW+koZewykv0yuO2HChTYeZtrULyHIDM9cj9TUXloCsueCQUog==",
"dev": true,
"funding": [
{
"type": "github",
@@ -3790,6 +4010,7 @@
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
"integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
"dev": true,
"license": "MIT"
},
"node_modules/flattie": {
@@ -3910,6 +4131,7 @@
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC"
},
"node_modules/h3": {
@@ -4031,6 +4253,7 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
"integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
@@ -4083,6 +4306,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz",
"integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==",
"dev": true,
"funding": [
{
"type": "github",
@@ -4095,6 +4319,7 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
"devOptional": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
@@ -4127,6 +4352,7 @@
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/lightningcss": {
@@ -4575,6 +4801,7 @@
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.8.6.tgz",
"integrity": "sha512-LYSks0tNVqbtez/9OB0YFP6qLRSqkdAxJWOQpVaR67lNTbj2zNLyYpowkRMia5lXLCZKzxwoxkxH2MbbBR2aEg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -4620,6 +4847,15 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/negotiator": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
"integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/neotraverse": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-1.0.1.tgz",
@@ -4711,6 +4947,15 @@
"node": ">= 0.8"
}
},
"node_modules/on-headers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/oniguruma-parser": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz",
@@ -4825,6 +5070,7 @@
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz",
"integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==",
"dev": true,
"funding": [
{
"type": "github",
@@ -4840,6 +5086,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"dev": true,
"license": "MIT"
},
"node_modules/piccolore": {
@@ -5410,6 +5657,26 @@
"dev": true,
"license": "MIT"
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/sass-formatter": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.8.0.tgz",
@@ -5616,6 +5883,7 @@
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz",
"integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "^24.9.2",
@@ -5735,6 +6003,7 @@
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.5.1.tgz",
"integrity": "sha512-UM4nTLWzQiDBseVxUGxN0wJzIk0UU89Qi7pKXz9qISo+lhviJikTuikBsK/oFRlpvYHvSdz0TVoLyQ+sEpYwxA==",
"dev": true,
"funding": [
{
"type": "github",
@@ -5833,12 +6102,14 @@
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
"integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
"dev": true,
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -5992,6 +6263,7 @@
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
"devOptional": true,
"license": "MIT"
},
"node_modules/unicode-trie": {
@@ -6207,6 +6479,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/verkit": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/verkit/-/verkit-0.5.0.tgz",
@@ -6697,6 +6978,7 @@
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz",
"integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==",
"dev": true,
"funding": [
{
"type": "github",
+14 -11
View File
@@ -3,7 +3,7 @@
"type": "module",
"version": "0.0.1",
"engines": {
"node": ">=22.22.0"
"node": ">=24"
},
"scripts": {
"dev": "astro dev",
@@ -16,29 +16,31 @@
"astro": "astro",
"icons": "node scripts/generate-icons.mjs",
"check:site": "node scripts/check-site.mjs",
"fonts": "node scripts/subset-fonts.mjs"
"fonts": "node scripts/subset-fonts.mjs",
"start": "node server.mjs"
},
"dependencies": {
"@astrojs/markdown-satteri": "^0.4.4",
"@astrojs/mdx": "^8.1.0",
"@astrojs/node": "^11.1.8",
"@astrojs/rss": "^4.0.19",
"@astrojs/sitemap": "^3.7.4",
"@iconify-json/tabler": "^1.2.42",
"@tailwindcss/vite": "^4.3.3",
"astro": "^7.4.0",
"astro-icon": "^1.2.0",
"leaflet": "^1.9.4",
"tailwindcss": "^4.3.3"
"compression": "^1.8.2"
},
"devDependencies": {
"@astrojs/check": "^0.9.10",
"@astrojs/markdown-satteri": "^0.4.4",
"@astrojs/mdx": "^8.1.0",
"@astrojs/rss": "^4.0.19",
"@astrojs/sitemap": "^3.7.4",
"@fontsource-variable/inter": "^5.3.0",
"@fontsource-variable/rubik": "^5.3.0",
"@fontsource/inter": "^5.3.0",
"@fontsource/rubik": "^5.3.0",
"@iconify-json/tabler": "^1.2.42",
"@tailwindcss/typography": "^0.5.20",
"@tailwindcss/vite": "^4.3.3",
"@types/compression": "^1.8.1",
"@types/leaflet": "^1.9.22",
"astro-icon": "^1.2.0",
"leaflet": "^1.9.4",
"pagefind": "^1.5.2",
"prettier": "^3.9.10",
"prettier-plugin-astro": "^1.1.1",
@@ -46,6 +48,7 @@
"satori": "^0.60.0",
"sharp": "^0.35.5",
"subset-font": "^2.9.0",
"tailwindcss": "^4.3.3",
"typescript": "^6.0.3"
}
}
+34
View File
@@ -0,0 +1,34 @@
// Production entry: wraps Astro's Node handler with compression, security headers and graceful shutdown.
// Run after `npm run build`: node server.mjs (env: HOST, PORT, DATA_DIR)
import http from 'node:http';
import compression from 'compression';
process.env.ASTRO_NODE_AUTOSTART = 'disabled';
const { handler } = await import('./dist/server/entry.mjs');
const HOST = process.env.HOST ?? '0.0.0.0';
const PORT = Number(process.env.PORT ?? 4321);
const compress = compression({ threshold: 1024 });
const securityHeaders = {
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'X-Frame-Options': 'SAMEORIGIN',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=(), interest-cohort=()',
};
const server = http.createServer((req, res) => {
for (const [k, v] of Object.entries(securityHeaders)) res.setHeader(k, v);
compress(req, res, () => handler(req, res));
});
server.listen(PORT, HOST, () => console.log(`ta1sph.info listening on http://${HOST}:${PORT}`));
// Docker/Dokploy send SIGTERM on redeploy: finish in-flight requests, then exit.
for (const signal of ['SIGTERM', 'SIGINT']) {
process.on(signal, () => {
server.close(() => process.exit(0));
setTimeout(() => process.exit(0), 10_000).unref();
});
}
+10
View File
@@ -64,3 +64,13 @@ export function clientIp(request: Request, fallback: string): string {
export const isBot = (ua: string | null) =>
!ua || /bot|crawl|spider|slurp|preview|facebookexternalhit|headless|lighthouse/i.test(ua);
/** Health probe: the database opens and answers a trivial query. */
export function dbHealthy(): boolean {
try {
open().prepare('SELECT 1').get();
return true;
} catch {
return false;
}
}
+13
View File
@@ -0,0 +1,13 @@
import type { APIRoute } from 'astro';
import { dbHealthy } from '../../lib/views';
export const prerender = false;
// GET /api/health — Docker HEALTHCHECK / Dokploy. 503 if the view-counter DB is unavailable.
export const GET: APIRoute = () => {
const db = dbHealthy();
return Response.json(
{ status: db ? 'ok' : 'degraded', db },
{ status: db ? 200 : 503, headers: { 'Cache-Control': 'no-store' } },
);
};