{post.title}
+ + +{excerpt}
+diff --git a/.github/workflows/astro.yml b/.github/workflows/astro.yml
deleted file mode 100644
index d403230..0000000
--- a/.github/workflows/astro.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: Deply Astro site to Pages
-
-on:
- push:
- branches: [main]
-
- workflow_dispatch:
-
-permissions:
- contents: read
- pages: write
- id-token: write
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout repository
- uses: actions/checkout@v3
- - name: Install, build, and upload blog
- uses: withastro/action@v5
- with:
- path: ./root
- deploy:
- needs: build
- runs-on: ubuntu-latest
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- steps:
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml
new file mode 100644
index 0000000..32beda7
--- /dev/null
+++ b/.github/workflows/deploy-backend.yml
@@ -0,0 +1,48 @@
+name: Deploy Backend
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - backend/**
+
+env:
+ REGION: asia-northeast1
+ REGISTRY: asia-northeast1-docker.pkg.dev/personal-blog-heo/blog
+ SERVICE: blog-backend
+
+jobs:
+ test:
+ uses: ./.github/workflows/test-backend.yml
+
+ deploy:
+ needs: test
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: google-github-actions/auth@v2
+ with:
+ workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
+ service_account: ${{ secrets.WIF_SERVICE_ACCOUNT }}
+
+ - uses: google-github-actions/setup-gcloud@v2
+
+ - name: Configure Docker
+ run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet
+
+ - name: Build and push
+ run: |
+ docker build -t ${{ env.REGISTRY }}/backend:${{ github.sha }} -t ${{ env.REGISTRY }}/backend:latest backend/
+ docker push ${{ env.REGISTRY }}/backend:${{ github.sha }}
+ docker push ${{ env.REGISTRY }}/backend:latest
+
+ - name: Deploy to Cloud Run
+ run: |
+ gcloud run services update ${{ env.SERVICE }} \
+ --region ${{ env.REGION }} \
+ --image ${{ env.REGISTRY }}/backend:${{ github.sha }}
diff --git a/.github/workflows/deploy-frontend.yml b/.github/workflows/deploy-frontend.yml
new file mode 100644
index 0000000..b57371e
--- /dev/null
+++ b/.github/workflows/deploy-frontend.yml
@@ -0,0 +1,44 @@
+name: Deploy Frontend
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - frontend/**
+
+env:
+ REGION: asia-northeast1
+ REGISTRY: asia-northeast1-docker.pkg.dev/personal-blog-heo/blog
+ SERVICE: blog-frontend
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: google-github-actions/auth@v2
+ with:
+ workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
+ service_account: ${{ secrets.WIF_SERVICE_ACCOUNT }}
+
+ - uses: google-github-actions/setup-gcloud@v2
+
+ - name: Configure Docker
+ run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet
+
+ - name: Build and push
+ run: |
+ docker build -t ${{ env.REGISTRY }}/frontend:${{ github.sha }} -t ${{ env.REGISTRY }}/frontend:latest frontend/
+ docker push ${{ env.REGISTRY }}/frontend:${{ github.sha }}
+ docker push ${{ env.REGISTRY }}/frontend:latest
+
+ - name: Deploy to Cloud Run
+ run: |
+ gcloud run services update ${{ env.SERVICE }} \
+ --region ${{ env.REGION }} \
+ --image ${{ env.REGISTRY }}/frontend:${{ github.sha }}
diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml
new file mode 100644
index 0000000..90885d1
--- /dev/null
+++ b/.github/workflows/test-backend.yml
@@ -0,0 +1,26 @@
+name: Test Backend
+
+on:
+ push:
+ paths:
+ - backend/**
+ pull_request:
+ paths:
+ - backend/**
+ workflow_call:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version-file: backend/go.mod
+ cache-dependency-path: backend/go.sum
+
+ - name: Run tests
+ working-directory: backend
+ run: go test -cover ./...
diff --git a/.gitignore b/.gitignore
index a8a4b73..6f8fc80 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,6 +27,9 @@ draft.*.md
.DS_Store
*.local
*.pem
+context.md
+cookies.txt
+hash.go
# debug
@@ -43,4 +46,10 @@ yarn-error.log*
# typescript
*.tsbuildinfo
-next-env.d.ts
\ No newline at end of file
+next-env.d.ts
+
+# terraform
+*.tfstate
+*.tfstate.*
+.terraform/
+terraform.tfvars
\ No newline at end of file
diff --git a/backend/Dockerfile b/backend/Dockerfile
new file mode 100644
index 0000000..6b72a57
--- /dev/null
+++ b/backend/Dockerfile
@@ -0,0 +1,20 @@
+FROM golang:1.25.7-alpine AS build
+
+WORKDIR /app
+
+COPY go.mod go.sum ./
+RUN go mod download
+
+COPY . .
+RUN CGO_ENABLED=0 go build -o server ./cmd/server
+
+FROM alpine:3.21
+
+WORKDIR /app
+
+COPY --from=build /app/server .
+COPY --from=build /app/migrations/ ./migrations/
+
+EXPOSE 8080
+
+CMD ["./server"]
diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go
new file mode 100644
index 0000000..ae5f98f
--- /dev/null
+++ b/backend/cmd/server/main.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+ "context"
+ "log"
+ "net/http"
+ "time"
+
+ "github.com/golang-migrate/migrate/v4"
+ _ "github.com/golang-migrate/migrate/v4/database/postgres"
+ _ "github.com/golang-migrate/migrate/v4/source/file"
+ "github.com/jackc/pgx/v5/pgxpool"
+ "github.com/joho/godotenv"
+ "github.com/yeahjun/blog/backend/internal/api"
+ "github.com/yeahjun/blog/backend/internal/auth"
+ "github.com/yeahjun/blog/backend/internal/config"
+ db "github.com/yeahjun/blog/backend/internal/db/generated"
+)
+
+func main() {
+ godotenv.Load()
+ cfg, err := config.Load()
+ if err != nil {
+ log.Fatalf("config error: %v", err)
+ }
+
+ m, err := migrate.New("file://migrations", cfg.DatabaseURL)
+ if err != nil {
+ log.Fatalf("migration error: %v", err)
+ }
+ if err := m.Up(); err != nil && err != migrate.ErrNoChange {
+ log.Fatalf("migration error: %v", err)
+ }
+ log.Println("migrations applied.")
+
+ pool, err := pgxpool.New(context.Background(), cfg.DatabaseURL)
+ if err != nil {
+ log.Fatalf("db connection error: %v", err)
+ }
+ err = pool.Ping(context.Background())
+ if err != nil {
+ log.Fatalf("db ping error: %v", err)
+ }
+ defer pool.Close()
+
+ queries := db.New(pool)
+
+ tokenConfig := auth.TokenConfig{
+ Secret: cfg.JWTSecret,
+ AccessTokenExpiry: 15 * time.Minute,
+ RefreshTokenExpiry: 30 * 24 * time.Hour,
+ }
+
+ handler := api.NewHandler(queries, tokenConfig, cfg.AllowedOrigin)
+ r := handler.Routes()
+
+ log.Printf("server is starting: %v", cfg.Port)
+ log.Fatal(http.ListenAndServe(":"+cfg.Port, r))
+}
diff --git a/backend/go.mod b/backend/go.mod
new file mode 100644
index 0000000..608c1b1
--- /dev/null
+++ b/backend/go.mod
@@ -0,0 +1,22 @@
+module github.com/yeahjun/blog/backend
+
+go 1.25.7
+
+require (
+ github.com/go-chi/chi/v5 v5.2.5
+ github.com/golang-jwt/jwt/v5 v5.3.1
+ github.com/golang-migrate/migrate/v4 v4.19.1
+ github.com/google/uuid v1.6.0
+ github.com/jackc/pgx/v5 v5.8.0
+ github.com/joho/godotenv v1.5.1
+ golang.org/x/crypto v0.45.0
+)
+
+require (
+ github.com/jackc/pgpassfile v1.0.0 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
+ github.com/jackc/puddle/v2 v2.2.2 // indirect
+ github.com/lib/pq v1.10.9 // indirect
+ golang.org/x/sync v0.18.0 // indirect
+ golang.org/x/text v0.31.0 // indirect
+)
diff --git a/backend/go.sum b/backend/go.sum
new file mode 100644
index 0000000..5428ea6
--- /dev/null
+++ b/backend/go.sum
@@ -0,0 +1,91 @@
+github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
+github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
+github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
+github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
+github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
+github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
+github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU=
+github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
+github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
+github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
+github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
+github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
+github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
+github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
+github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
+github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
+github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
+github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
+github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
+github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
+github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
+github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
+github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
+github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
+go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
+go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
+go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
+go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
+go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
+go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/backend/internal/api/auth.go b/backend/internal/api/auth.go
new file mode 100644
index 0000000..6fde743
--- /dev/null
+++ b/backend/internal/api/auth.go
@@ -0,0 +1,198 @@
+package api
+
+import (
+ "encoding/json"
+ "net/http"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/jackc/pgx/v5/pgtype"
+ "github.com/yeahjun/blog/backend/internal/auth"
+ db "github.com/yeahjun/blog/backend/internal/db/generated"
+)
+
+type loginRequest struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+}
+
+func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
+ var req loginRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ author, err := h.sqlc.GetAuthorByUsername(r.Context(), req.Username)
+ if err != nil {
+ http.Error(w, "invalid credentials", http.StatusUnauthorized)
+ return
+ }
+
+ if !auth.ComparePassword(author.PasswordHash, req.Password) {
+ http.Error(w, "invalid credentials", http.StatusUnauthorized)
+ return
+ }
+
+ accessToken, err := auth.GenerateAccessToken(uuid.UUID(author.ID.Bytes).String(), h.tokenConfig)
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ refreshToken, err := auth.GenerateRefreshToken()
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ _, err = h.sqlc.CreateRefreshToken(r.Context(), db.CreateRefreshTokenParams{
+ AuthorID: author.ID,
+ TokenHash: auth.HashToken(refreshToken),
+ ExpiresAt: pgtype.Timestamptz{
+ Time: time.Now().Add(h.tokenConfig.RefreshTokenExpiry),
+ Valid: true,
+ },
+ })
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ http.SetCookie(w, &http.Cookie{
+ Name: "access_token",
+ Value: accessToken,
+ Path: "/",
+ HttpOnly: true,
+ Secure: true,
+ SameSite: http.SameSiteStrictMode,
+ MaxAge: int(h.tokenConfig.AccessTokenExpiry.Seconds()),
+ })
+
+ http.SetCookie(w, &http.Cookie{
+ Name: "refresh_token",
+ Value: refreshToken,
+ Path: "/api/auth",
+ HttpOnly: true,
+ Secure: true,
+ SameSite: http.SameSiteStrictMode,
+ MaxAge: int(h.tokenConfig.RefreshTokenExpiry.Seconds()),
+ })
+
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(map[string]string{"message": "logged in"})
+}
+
+func (h *Handler) Logout(w http.ResponseWriter, r *http.Request) {
+ cookie, err := r.Cookie("refresh_token")
+ if err != nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ err = h.sqlc.DeleteRefreshToken(r.Context(), auth.HashToken(cookie.Value))
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ http.SetCookie(w, &http.Cookie{
+ Name: "access_token",
+ Value: "",
+ Path: "/",
+ HttpOnly: true,
+ Secure: true,
+ SameSite: http.SameSiteStrictMode,
+ MaxAge: -1,
+ })
+
+ http.SetCookie(w, &http.Cookie{
+ Name: "refresh_token",
+ Value: "",
+ Path: "/api/auth",
+ HttpOnly: true,
+ Secure: true,
+ SameSite: http.SameSiteStrictMode,
+ MaxAge: -1,
+ })
+
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(map[string]string{"message": "logged out"})
+}
+
+func (h *Handler) Refresh(w http.ResponseWriter, r *http.Request) {
+ cookie, err := r.Cookie("refresh_token")
+ if err != nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ tokenHash := auth.HashToken(cookie.Value)
+ stored, err := h.sqlc.GetRefreshToken(r.Context(), tokenHash)
+ if err != nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ if time.Now().After(stored.ExpiresAt.Time) {
+ h.sqlc.DeleteRefreshToken(r.Context(), tokenHash)
+ http.Error(w, "token expired", http.StatusUnauthorized)
+ return
+ }
+
+ err = h.sqlc.DeleteRefreshToken(r.Context(), tokenHash)
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ authorID := uuid.UUID(stored.AuthorID.Bytes).String()
+
+ accessToken, err := auth.GenerateAccessToken(authorID, h.tokenConfig)
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ newRefreshToken, err := auth.GenerateRefreshToken()
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ _, err = h.sqlc.CreateRefreshToken(r.Context(), db.CreateRefreshTokenParams{
+ AuthorID: stored.AuthorID,
+ TokenHash: auth.HashToken(newRefreshToken),
+ ExpiresAt: pgtype.Timestamptz{
+ Time: time.Now().Add(h.tokenConfig.RefreshTokenExpiry),
+ Valid: true,
+ },
+ })
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ http.SetCookie(w, &http.Cookie{
+ Name: "access_token",
+ Value: accessToken,
+ Path: "/",
+ HttpOnly: true,
+ Secure: true,
+ SameSite: http.SameSiteStrictMode,
+ MaxAge: int(h.tokenConfig.AccessTokenExpiry.Seconds()),
+ })
+
+ http.SetCookie(w, &http.Cookie{
+ Name: "refresh_token",
+ Value: newRefreshToken,
+ Path: "/api/auth",
+ HttpOnly: true,
+ Secure: true,
+ SameSite: http.SameSiteStrictMode,
+ MaxAge: int(h.tokenConfig.RefreshTokenExpiry.Seconds()),
+ })
+
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(map[string]string{"message": "refreshed"})
+}
diff --git a/backend/internal/api/auth_test.go b/backend/internal/api/auth_test.go
new file mode 100644
index 0000000..6b20a68
--- /dev/null
+++ b/backend/internal/api/auth_test.go
@@ -0,0 +1,228 @@
+package api
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/jackc/pgx/v5/pgtype"
+ "github.com/yeahjun/blog/backend/internal/auth"
+ db "github.com/yeahjun/blog/backend/internal/db/generated"
+)
+
+func TestLogin_Success(t *testing.T) {
+ hashed, _ := auth.HashPassword("password123")
+ authorID := pgtype.UUID{Bytes: [16]byte{1}, Valid: true}
+
+ mock := &mockQuerier{
+ GetAuthorByUsernameFunc: func(ctx context.Context, username string) (db.Author, error) {
+ return db.Author{
+ ID: authorID,
+ Username: "admin",
+ PasswordHash: hashed,
+ }, nil
+ },
+ CreateRefreshTokenFunc: func(ctx context.Context, arg db.CreateRefreshTokenParams) (db.RefreshToken, error) {
+ return db.RefreshToken{}, nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ body := `{"username":"admin","password":"password123"}`
+ req := httptest.NewRequest(http.MethodPost, "/api/auth/login", strings.NewReader(body))
+ rec := httptest.NewRecorder()
+
+ h.Login(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected 200, got %d", rec.Code)
+ }
+
+ cookies := rec.Result().Cookies()
+ var hasAccess, hasRefresh bool
+ for _, c := range cookies {
+ if c.Name == "access_token" && c.Value != "" {
+ hasAccess = true
+ }
+ if c.Name == "refresh_token" && c.Value != "" {
+ hasRefresh = true
+ }
+ }
+ if !hasAccess {
+ t.Fatal("expected access_token cookie")
+ }
+ if !hasRefresh {
+ t.Fatal("expected refresh_token cookie")
+ }
+}
+
+func TestLogin_BadJSON(t *testing.T) {
+ h := newTestHandler(&mockQuerier{})
+ req := httptest.NewRequest(http.MethodPost, "/api/auth/login", strings.NewReader("not json"))
+ rec := httptest.NewRecorder()
+
+ h.Login(rec, req)
+
+ if rec.Code != http.StatusBadRequest {
+ t.Fatalf("expected 400, got %d", rec.Code)
+ }
+}
+
+func TestLogin_UserNotFound(t *testing.T) {
+ mock := &mockQuerier{
+ GetAuthorByUsernameFunc: func(ctx context.Context, username string) (db.Author, error) {
+ return db.Author{}, fmt.Errorf("no rows")
+ },
+ }
+
+ h := newTestHandler(mock)
+ body := `{"username":"unknown","password":"pass"}`
+ req := httptest.NewRequest(http.MethodPost, "/api/auth/login", strings.NewReader(body))
+ rec := httptest.NewRecorder()
+
+ h.Login(rec, req)
+
+ if rec.Code != http.StatusUnauthorized {
+ t.Fatalf("expected 401, got %d", rec.Code)
+ }
+}
+
+func TestLogin_WrongPassword(t *testing.T) {
+ hashed, _ := auth.HashPassword("correct")
+ mock := &mockQuerier{
+ GetAuthorByUsernameFunc: func(ctx context.Context, username string) (db.Author, error) {
+ return db.Author{
+ ID: pgtype.UUID{Bytes: [16]byte{1}, Valid: true},
+ Username: "admin",
+ PasswordHash: hashed,
+ }, nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ body := `{"username":"admin","password":"wrong"}`
+ req := httptest.NewRequest(http.MethodPost, "/api/auth/login", strings.NewReader(body))
+ rec := httptest.NewRecorder()
+
+ h.Login(rec, req)
+
+ if rec.Code != http.StatusUnauthorized {
+ t.Fatalf("expected 401, got %d", rec.Code)
+ }
+}
+
+func TestLogout_Success(t *testing.T) {
+ mock := &mockQuerier{
+ DeleteRefreshTokenFunc: func(ctx context.Context, tokenHash string) error {
+ return nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ req := httptest.NewRequest(http.MethodPost, "/api/auth/logout", nil)
+ req.AddCookie(&http.Cookie{Name: "refresh_token", Value: "some-token"})
+ rec := httptest.NewRecorder()
+
+ h.Logout(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected 200, got %d", rec.Code)
+ }
+
+ cookies := rec.Result().Cookies()
+ for _, c := range cookies {
+ if c.Name == "access_token" && c.MaxAge != -1 {
+ t.Fatal("expected access_token cookie to be cleared")
+ }
+ if c.Name == "refresh_token" && c.MaxAge != -1 {
+ t.Fatal("expected refresh_token cookie to be cleared")
+ }
+ }
+}
+
+func TestLogout_NoCookie(t *testing.T) {
+ h := newTestHandler(&mockQuerier{})
+ req := httptest.NewRequest(http.MethodPost, "/api/auth/logout", nil)
+ rec := httptest.NewRecorder()
+
+ h.Logout(rec, req)
+
+ if rec.Code != http.StatusUnauthorized {
+ t.Fatalf("expected 401, got %d", rec.Code)
+ }
+}
+
+func TestRefresh_Success(t *testing.T) {
+ authorID := pgtype.UUID{Bytes: [16]byte{2}, Valid: true}
+ rawToken := "original-refresh-token"
+
+ mock := &mockQuerier{
+ GetRefreshTokenFunc: func(ctx context.Context, tokenHash string) (db.RefreshToken, error) {
+ return db.RefreshToken{
+ AuthorID: authorID,
+ TokenHash: tokenHash,
+ ExpiresAt: pgtype.Timestamptz{
+ Time: time.Now().Add(24 * time.Hour),
+ Valid: true,
+ },
+ }, nil
+ },
+ DeleteRefreshTokenFunc: func(ctx context.Context, tokenHash string) error {
+ return nil
+ },
+ CreateRefreshTokenFunc: func(ctx context.Context, arg db.CreateRefreshTokenParams) (db.RefreshToken, error) {
+ return db.RefreshToken{}, nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ req := httptest.NewRequest(http.MethodPost, "/api/auth/refresh", nil)
+ req.AddCookie(&http.Cookie{Name: "refresh_token", Value: rawToken})
+ rec := httptest.NewRecorder()
+
+ h.Refresh(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected 200, got %d", rec.Code)
+ }
+
+ var resp map[string]string
+ json.NewDecoder(rec.Body).Decode(&resp)
+ if resp["message"] != "refreshed" {
+ t.Fatalf("expected 'refreshed' message, got %v", resp)
+ }
+}
+
+func TestRefresh_Expired(t *testing.T) {
+ mock := &mockQuerier{
+ GetRefreshTokenFunc: func(ctx context.Context, tokenHash string) (db.RefreshToken, error) {
+ return db.RefreshToken{
+ AuthorID: pgtype.UUID{Bytes: [16]byte{3}, Valid: true},
+ TokenHash: tokenHash,
+ ExpiresAt: pgtype.Timestamptz{
+ Time: time.Now().Add(-1 * time.Hour),
+ Valid: true,
+ },
+ }, nil
+ },
+ DeleteRefreshTokenFunc: func(ctx context.Context, tokenHash string) error {
+ return nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ req := httptest.NewRequest(http.MethodPost, "/api/auth/refresh", nil)
+ req.AddCookie(&http.Cookie{Name: "refresh_token", Value: "expired-token"})
+ rec := httptest.NewRecorder()
+
+ h.Refresh(rec, req)
+
+ if rec.Code != http.StatusUnauthorized {
+ t.Fatalf("expected 401, got %d", rec.Code)
+ }
+}
diff --git a/backend/internal/api/cors.go b/backend/internal/api/cors.go
new file mode 100644
index 0000000..a3ca6a6
--- /dev/null
+++ b/backend/internal/api/cors.go
@@ -0,0 +1,21 @@
+package api
+
+import "net/http"
+
+func CORSMiddleware(origin string) func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Origin", origin)
+ w.Header().Set("Access-Control-Allow-Credentials", "true")
+ w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
+ w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
+
+ if r.Method == http.MethodOptions {
+ w.WriteHeader(http.StatusNoContent)
+ return
+ }
+
+ next.ServeHTTP(w, r)
+ })
+ }
+}
diff --git a/backend/internal/api/handler.go b/backend/internal/api/handler.go
new file mode 100644
index 0000000..27e7ffb
--- /dev/null
+++ b/backend/internal/api/handler.go
@@ -0,0 +1,20 @@
+package api
+
+import (
+ "github.com/yeahjun/blog/backend/internal/auth"
+ db "github.com/yeahjun/blog/backend/internal/db/generated"
+)
+
+type Handler struct {
+ sqlc db.Querier
+ tokenConfig auth.TokenConfig
+ allowedOrigin string
+}
+
+func NewHandler(queries db.Querier, tokenConfig auth.TokenConfig, allowedOrigin string) *Handler {
+ return &Handler{
+ sqlc: queries,
+ tokenConfig: tokenConfig,
+ allowedOrigin: allowedOrigin,
+ }
+}
diff --git a/backend/internal/api/mock_test.go b/backend/internal/api/mock_test.go
new file mode 100644
index 0000000..6ea757b
--- /dev/null
+++ b/backend/internal/api/mock_test.go
@@ -0,0 +1,102 @@
+package api
+
+import (
+ "context"
+ "time"
+
+ "github.com/jackc/pgx/v5/pgtype"
+ "github.com/yeahjun/blog/backend/internal/auth"
+ db "github.com/yeahjun/blog/backend/internal/db/generated"
+)
+
+type mockQuerier struct {
+ AddTagToPostFunc func(ctx context.Context, arg db.AddTagToPostParams) error
+ CreateAuthorFunc func(ctx context.Context, arg db.CreateAuthorParams) (db.Author, error)
+ CreatePostFunc func(ctx context.Context, arg db.CreatePostParams) (db.Post, error)
+ CreateRefreshTokenFunc func(ctx context.Context, arg db.CreateRefreshTokenParams) (db.RefreshToken, error)
+ CreateTagFunc func(ctx context.Context, arg db.CreateTagParams) (db.Tag, error)
+ DeletePostByIDFunc func(ctx context.Context, id pgtype.UUID) error
+ DeleteRefreshTokenFunc func(ctx context.Context, tokenHash string) error
+ DeleteRefreshTokenByAuthorFunc func(ctx context.Context, authorID pgtype.UUID) error
+ DeleteTagByIDFunc func(ctx context.Context, id pgtype.UUID) error
+ GetAllTagsFunc func(ctx context.Context) ([]db.Tag, error)
+ GetAuthorByUsernameFunc func(ctx context.Context, username string) (db.Author, error)
+ GetPostBySlugFunc func(ctx context.Context, slug string) (db.Post, error)
+ GetPostByTagSlugFunc func(ctx context.Context, arg db.GetPostByTagSlugParams) ([]db.GetPostByTagSlugRow, error)
+ GetPostTagsFunc func(ctx context.Context, postID pgtype.UUID) ([]db.GetPostTagsRow, error)
+ GetPostsAdminFunc func(ctx context.Context, arg db.GetPostsAdminParams) ([]db.Post, error)
+ GetPublishedPostsFunc func(ctx context.Context, arg db.GetPublishedPostsParams) ([]db.Post, error)
+ GetRefreshTokenFunc func(ctx context.Context, tokenHash string) (db.RefreshToken, error)
+ RemoveTagFromPostFunc func(ctx context.Context, arg db.RemoveTagFromPostParams) error
+ UpdatePostByIDFunc func(ctx context.Context, arg db.UpdatePostByIDParams) (db.Post, error)
+}
+
+func (m *mockQuerier) AddTagToPost(ctx context.Context, arg db.AddTagToPostParams) error {
+ return m.AddTagToPostFunc(ctx, arg)
+}
+func (m *mockQuerier) CreateAuthor(ctx context.Context, arg db.CreateAuthorParams) (db.Author, error) {
+ return m.CreateAuthorFunc(ctx, arg)
+}
+func (m *mockQuerier) CreatePost(ctx context.Context, arg db.CreatePostParams) (db.Post, error) {
+ return m.CreatePostFunc(ctx, arg)
+}
+func (m *mockQuerier) CreateRefreshToken(ctx context.Context, arg db.CreateRefreshTokenParams) (db.RefreshToken, error) {
+ return m.CreateRefreshTokenFunc(ctx, arg)
+}
+func (m *mockQuerier) CreateTag(ctx context.Context, arg db.CreateTagParams) (db.Tag, error) {
+ return m.CreateTagFunc(ctx, arg)
+}
+func (m *mockQuerier) DeletePostByID(ctx context.Context, id pgtype.UUID) error {
+ return m.DeletePostByIDFunc(ctx, id)
+}
+func (m *mockQuerier) DeleteRefreshToken(ctx context.Context, tokenHash string) error {
+ return m.DeleteRefreshTokenFunc(ctx, tokenHash)
+}
+func (m *mockQuerier) DeleteRefreshTokenByAuthor(ctx context.Context, authorID pgtype.UUID) error {
+ return m.DeleteRefreshTokenByAuthorFunc(ctx, authorID)
+}
+func (m *mockQuerier) DeleteTagByID(ctx context.Context, id pgtype.UUID) error {
+ return m.DeleteTagByIDFunc(ctx, id)
+}
+func (m *mockQuerier) GetAllTags(ctx context.Context) ([]db.Tag, error) {
+ return m.GetAllTagsFunc(ctx)
+}
+func (m *mockQuerier) GetAuthorByUsername(ctx context.Context, username string) (db.Author, error) {
+ return m.GetAuthorByUsernameFunc(ctx, username)
+}
+func (m *mockQuerier) GetPostBySlug(ctx context.Context, slug string) (db.Post, error) {
+ return m.GetPostBySlugFunc(ctx, slug)
+}
+func (m *mockQuerier) GetPostByTagSlug(ctx context.Context, arg db.GetPostByTagSlugParams) ([]db.GetPostByTagSlugRow, error) {
+ return m.GetPostByTagSlugFunc(ctx, arg)
+}
+func (m *mockQuerier) GetPostTags(ctx context.Context, postID pgtype.UUID) ([]db.GetPostTagsRow, error) {
+ return m.GetPostTagsFunc(ctx, postID)
+}
+func (m *mockQuerier) GetPostsAdmin(ctx context.Context, arg db.GetPostsAdminParams) ([]db.Post, error) {
+ return m.GetPostsAdminFunc(ctx, arg)
+}
+func (m *mockQuerier) GetPublishedPosts(ctx context.Context, arg db.GetPublishedPostsParams) ([]db.Post, error) {
+ return m.GetPublishedPostsFunc(ctx, arg)
+}
+func (m *mockQuerier) GetRefreshToken(ctx context.Context, tokenHash string) (db.RefreshToken, error) {
+ return m.GetRefreshTokenFunc(ctx, tokenHash)
+}
+func (m *mockQuerier) RemoveTagFromPost(ctx context.Context, arg db.RemoveTagFromPostParams) error {
+ return m.RemoveTagFromPostFunc(ctx, arg)
+}
+func (m *mockQuerier) UpdatePostByID(ctx context.Context, arg db.UpdatePostByIDParams) (db.Post, error) {
+ return m.UpdatePostByIDFunc(ctx, arg)
+}
+
+func newTestHandler(mock *mockQuerier) *Handler {
+ return &Handler{
+ sqlc: mock,
+ tokenConfig: auth.TokenConfig{
+ Secret: "test-secret",
+ AccessTokenExpiry: 15 * time.Minute,
+ RefreshTokenExpiry: 7 * 24 * time.Hour,
+ },
+ allowedOrigin: "http://localhost:4321",
+ }
+}
diff --git a/backend/internal/api/posts.go b/backend/internal/api/posts.go
new file mode 100644
index 0000000..616f7fa
--- /dev/null
+++ b/backend/internal/api/posts.go
@@ -0,0 +1,130 @@
+package api
+
+import (
+ "encoding/json"
+ "net/http"
+ "strconv"
+
+ "github.com/go-chi/chi/v5"
+ "github.com/jackc/pgx/v5/pgtype"
+ db "github.com/yeahjun/blog/backend/internal/db/generated"
+)
+
+func (h *Handler) ListPublishedPosts(w http.ResponseWriter, r *http.Request) {
+ limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
+ offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
+ if limit <= 0 {
+ limit = 10
+ }
+
+ posts, err := h.sqlc.GetPublishedPosts(r.Context(), db.GetPublishedPostsParams{
+ Limit: int32(limit),
+ Offset: int32(offset),
+ })
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(posts)
+}
+
+func (h *Handler) GetPost(w http.ResponseWriter, r *http.Request) {
+ slug := chi.URLParam(r, "slug")
+
+ post, err := h.sqlc.GetPostBySlug(r.Context(), slug)
+ if err != nil {
+ http.Error(w, "post not found", http.StatusNotFound)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(post)
+}
+
+func (h *Handler) ListPostsAdmin(w http.ResponseWriter, r *http.Request) {
+ limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
+ offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
+ if limit <= 0 {
+ limit = 50
+ }
+
+ posts, err := h.sqlc.GetPostsAdmin(r.Context(), db.GetPostsAdminParams{
+ Limit: int32(limit),
+ Offset: int32(offset),
+ })
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(posts)
+}
+
+func (h *Handler) CreatePost(w http.ResponseWriter, r *http.Request) {
+ var req db.CreatePostParams
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ post, err := h.sqlc.CreatePost(r.Context(), req)
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ json.NewEncoder(w).Encode(post)
+}
+
+func (h *Handler) UpdatePost(w http.ResponseWriter, r *http.Request) {
+ idStr := chi.URLParam(r, "id")
+ id, err := parseUUID(idStr)
+ if err != nil {
+ http.Error(w, "invalid id", http.StatusBadRequest)
+ return
+ }
+
+ var req db.UpdatePostByIDParams
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "invalid request body", http.StatusBadRequest)
+ return
+ }
+ req.ID = id
+
+ post, err := h.sqlc.UpdatePostByID(r.Context(), req)
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(post)
+}
+
+func (h *Handler) DeletePost(w http.ResponseWriter, r *http.Request) {
+ idStr := chi.URLParam(r, "id")
+ id, err := parseUUID(idStr)
+ if err != nil {
+ http.Error(w, "invalid id", http.StatusBadRequest)
+ return
+ }
+
+ err = h.sqlc.DeletePostByID(r.Context(), id)
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
+
+func parseUUID(s string) (pgtype.UUID, error) {
+ var id pgtype.UUID
+ err := id.Scan(s)
+ return id, err
+}
diff --git a/backend/internal/api/posts_test.go b/backend/internal/api/posts_test.go
new file mode 100644
index 0000000..3214108
--- /dev/null
+++ b/backend/internal/api/posts_test.go
@@ -0,0 +1,213 @@
+package api
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/go-chi/chi/v5"
+ "github.com/jackc/pgx/v5/pgtype"
+ db "github.com/yeahjun/blog/backend/internal/db/generated"
+)
+
+func TestListPublishedPosts_Success(t *testing.T) {
+ mock := &mockQuerier{
+ GetPublishedPostsFunc: func(ctx context.Context, arg db.GetPublishedPostsParams) ([]db.Post, error) {
+ return []db.Post{
+ {Title: "Post 1", Slug: "post-1"},
+ {Title: "Post 2", Slug: "post-2"},
+ }, nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ req := httptest.NewRequest(http.MethodGet, "/api/posts?limit=5", nil)
+ rec := httptest.NewRecorder()
+
+ h.ListPublishedPosts(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected 200, got %d", rec.Code)
+ }
+
+ var posts []db.Post
+ json.NewDecoder(rec.Body).Decode(&posts)
+ if len(posts) != 2 {
+ t.Fatalf("expected 2 posts, got %d", len(posts))
+ }
+}
+
+func TestListPublishedPosts_DefaultLimit(t *testing.T) {
+ var gotLimit int32
+ mock := &mockQuerier{
+ GetPublishedPostsFunc: func(ctx context.Context, arg db.GetPublishedPostsParams) ([]db.Post, error) {
+ gotLimit = arg.Limit
+ return []db.Post{}, nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ req := httptest.NewRequest(http.MethodGet, "/api/posts", nil)
+ rec := httptest.NewRecorder()
+
+ h.ListPublishedPosts(rec, req)
+
+ if gotLimit != 10 {
+ t.Fatalf("expected default limit 10, got %d", gotLimit)
+ }
+}
+
+func TestGetPost_Success(t *testing.T) {
+ mock := &mockQuerier{
+ GetPostBySlugFunc: func(ctx context.Context, slug string) (db.Post, error) {
+ return db.Post{Title: "My Post", Slug: slug}, nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ req := httptest.NewRequest(http.MethodGet, "/api/posts/my-post", nil)
+ rctx := chi.NewRouteContext()
+ rctx.URLParams.Add("slug", "my-post")
+ req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
+ rec := httptest.NewRecorder()
+
+ h.GetPost(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected 200, got %d", rec.Code)
+ }
+}
+
+func TestGetPost_NotFound(t *testing.T) {
+ mock := &mockQuerier{
+ GetPostBySlugFunc: func(ctx context.Context, slug string) (db.Post, error) {
+ return db.Post{}, fmt.Errorf("no rows")
+ },
+ }
+
+ h := newTestHandler(mock)
+ req := httptest.NewRequest(http.MethodGet, "/api/posts/nonexistent", nil)
+ rctx := chi.NewRouteContext()
+ rctx.URLParams.Add("slug", "nonexistent")
+ req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
+ rec := httptest.NewRecorder()
+
+ h.GetPost(rec, req)
+
+ if rec.Code != http.StatusNotFound {
+ t.Fatalf("expected 404, got %d", rec.Code)
+ }
+}
+
+func TestCreatePost_Success(t *testing.T) {
+ mock := &mockQuerier{
+ CreatePostFunc: func(ctx context.Context, arg db.CreatePostParams) (db.Post, error) {
+ return db.Post{
+ Title: arg.Title,
+ Slug: arg.Slug,
+ Content: arg.Content,
+ Status: arg.Status,
+ }, nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ body := `{"title":"New Post","slug":"new-post","content":"Hello","status":"draft"}`
+ req := httptest.NewRequest(http.MethodPost, "/api/admin/posts", strings.NewReader(body))
+ rec := httptest.NewRecorder()
+
+ h.CreatePost(rec, req)
+
+ if rec.Code != http.StatusCreated {
+ t.Fatalf("expected 201, got %d", rec.Code)
+ }
+}
+
+func TestCreatePost_BadJSON(t *testing.T) {
+ h := newTestHandler(&mockQuerier{})
+ req := httptest.NewRequest(http.MethodPost, "/api/admin/posts", strings.NewReader("not json"))
+ rec := httptest.NewRecorder()
+
+ h.CreatePost(rec, req)
+
+ if rec.Code != http.StatusBadRequest {
+ t.Fatalf("expected 400, got %d", rec.Code)
+ }
+}
+
+func TestUpdatePost_Success(t *testing.T) {
+ mock := &mockQuerier{
+ UpdatePostByIDFunc: func(ctx context.Context, arg db.UpdatePostByIDParams) (db.Post, error) {
+ return db.Post{Title: arg.Title, Slug: arg.Slug}, nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ body := `{"title":"Updated","slug":"updated","content":"new content","status":"published"}`
+ req := httptest.NewRequest(http.MethodPut, "/api/admin/posts/550e8400-e29b-41d4-a716-446655440000", strings.NewReader(body))
+ rctx := chi.NewRouteContext()
+ rctx.URLParams.Add("id", "550e8400-e29b-41d4-a716-446655440000")
+ req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
+ rec := httptest.NewRecorder()
+
+ h.UpdatePost(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected 200, got %d", rec.Code)
+ }
+}
+
+func TestUpdatePost_BadUUID(t *testing.T) {
+ h := newTestHandler(&mockQuerier{})
+ req := httptest.NewRequest(http.MethodPut, "/api/admin/posts/bad-uuid", strings.NewReader(`{}`))
+ rctx := chi.NewRouteContext()
+ rctx.URLParams.Add("id", "bad-uuid")
+ req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
+ rec := httptest.NewRecorder()
+
+ h.UpdatePost(rec, req)
+
+ if rec.Code != http.StatusBadRequest {
+ t.Fatalf("expected 400, got %d", rec.Code)
+ }
+}
+
+func TestDeletePost_Success(t *testing.T) {
+ mock := &mockQuerier{
+ DeletePostByIDFunc: func(ctx context.Context, id pgtype.UUID) error {
+ return nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ req := httptest.NewRequest(http.MethodDelete, "/api/admin/posts/550e8400-e29b-41d4-a716-446655440000", nil)
+ rctx := chi.NewRouteContext()
+ rctx.URLParams.Add("id", "550e8400-e29b-41d4-a716-446655440000")
+ req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
+ rec := httptest.NewRecorder()
+
+ h.DeletePost(rec, req)
+
+ if rec.Code != http.StatusNoContent {
+ t.Fatalf("expected 204, got %d", rec.Code)
+ }
+}
+
+func TestDeletePost_BadUUID(t *testing.T) {
+ h := newTestHandler(&mockQuerier{})
+ req := httptest.NewRequest(http.MethodDelete, "/api/admin/posts/bad-uuid", nil)
+ rctx := chi.NewRouteContext()
+ rctx.URLParams.Add("id", "bad-uuid")
+ req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
+ rec := httptest.NewRecorder()
+
+ h.DeletePost(rec, req)
+
+ if rec.Code != http.StatusBadRequest {
+ t.Fatalf("expected 400, got %d", rec.Code)
+ }
+}
diff --git a/backend/internal/api/routes.go b/backend/internal/api/routes.go
new file mode 100644
index 0000000..d20f571
--- /dev/null
+++ b/backend/internal/api/routes.go
@@ -0,0 +1,48 @@
+package api
+
+import (
+ "time"
+
+ "github.com/go-chi/chi/v5"
+ "github.com/go-chi/chi/v5/middleware"
+ "github.com/yeahjun/blog/backend/internal/auth"
+)
+
+func (h *Handler) Routes() *chi.Mux {
+ r := chi.NewRouter()
+ globalLimiter := auth.NewRateLimiter(60, 1*time.Minute)
+ authLimiter := auth.NewRateLimiter(5, 1*time.Minute)
+ r.Use(middleware.Logger)
+ r.Use(middleware.Recoverer)
+ r.Use(CORSMiddleware(h.allowedOrigin))
+ r.Use(globalLimiter.Limit)
+
+ r.Route("/api", func(r chi.Router) {
+ r.Get("/posts", h.ListPublishedPosts)
+ r.Get("/posts/{slug}", h.GetPost)
+ r.Get("/tags", h.ListTags)
+ r.Get("/tags/{slug}/posts", h.GetPostsByTag)
+
+ r.Route("/auth", func(r chi.Router) {
+ r.Use(authLimiter.Limit)
+ r.Post("/login", h.Login)
+ r.Post("/logout", h.Logout)
+ r.Post("/refresh", h.Refresh)
+ })
+
+ r.Route("/admin", func(r chi.Router) {
+ r.Use(auth.JWTMiddleware(h.tokenConfig.Secret))
+ r.Get("/posts", h.ListPostsAdmin)
+ r.Post("/posts", h.CreatePost)
+ r.Put("/posts/{id}", h.UpdatePost)
+ r.Delete("/posts/{id}", h.DeletePost)
+ r.Get("/posts/{id}/tags", h.GetPostTags)
+ r.Post("/tags", h.CreateTag)
+ r.Delete("/tags/{id}", h.DeleteTag)
+ r.Post("/posts/{id}/tags", h.AddTagToPost)
+ r.Delete("/posts/{id}/tags/{tagID}", h.RemoveTagFromPost)
+ })
+ })
+
+ return r
+}
diff --git a/backend/internal/api/tags.go b/backend/internal/api/tags.go
new file mode 100644
index 0000000..fe04601
--- /dev/null
+++ b/backend/internal/api/tags.go
@@ -0,0 +1,157 @@
+package api
+
+import (
+ "encoding/json"
+ "net/http"
+ "strconv"
+
+ "github.com/go-chi/chi/v5"
+ db "github.com/yeahjun/blog/backend/internal/db/generated"
+)
+
+func (h *Handler) ListTags(w http.ResponseWriter, r *http.Request) {
+ tags, err := h.sqlc.GetAllTags(r.Context())
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(tags)
+}
+
+func (h *Handler) GetPostsByTag(w http.ResponseWriter, r *http.Request) {
+ slug := chi.URLParam(r, "slug")
+ limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
+ offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
+ if limit <= 0 {
+ limit = 10
+ }
+
+ posts, err := h.sqlc.GetPostByTagSlug(r.Context(), db.GetPostByTagSlugParams{
+ Slug: slug,
+ Limit: int32(limit),
+ Offset: int32(offset),
+ })
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(posts)
+}
+
+func (h *Handler) GetPostTags(w http.ResponseWriter, r *http.Request) {
+ idStr := chi.URLParam(r, "id")
+ id, err := parseUUID(idStr)
+ if err != nil {
+ http.Error(w, "invalid id", http.StatusBadRequest)
+ return
+ }
+
+ tags, err := h.sqlc.GetPostTags(r.Context(), id)
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(tags)
+}
+
+func (h *Handler) CreateTag(w http.ResponseWriter, r *http.Request) {
+ var req db.CreateTagParams
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ tag, err := h.sqlc.CreateTag(r.Context(), req)
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ json.NewEncoder(w).Encode(tag)
+}
+
+func (h *Handler) DeleteTag(w http.ResponseWriter, r *http.Request) {
+ idStr := chi.URLParam(r, "id")
+ id, err := parseUUID(idStr)
+ if err != nil {
+ http.Error(w, "invalid id", http.StatusBadRequest)
+ return
+ }
+
+ err = h.sqlc.DeleteTagByID(r.Context(), id)
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
+
+func (h *Handler) AddTagToPost(w http.ResponseWriter, r *http.Request) {
+ postIDStr := chi.URLParam(r, "id")
+ postID, err := parseUUID(postIDStr)
+ if err != nil {
+ http.Error(w, "invalid post id", http.StatusBadRequest)
+ return
+ }
+
+ var req struct {
+ TagID string `json:"tag_id"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ tagID, err := parseUUID(req.TagID)
+ if err != nil {
+ http.Error(w, "invalid tag id", http.StatusBadRequest)
+ return
+ }
+
+ err = h.sqlc.AddTagToPost(r.Context(), db.AddTagToPostParams{
+ PostID: postID,
+ TagID: tagID,
+ })
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusCreated)
+}
+
+func (h *Handler) RemoveTagFromPost(w http.ResponseWriter, r *http.Request) {
+ postIDStr := chi.URLParam(r, "id")
+ postID, err := parseUUID(postIDStr)
+ if err != nil {
+ http.Error(w, "invalid post id", http.StatusBadRequest)
+ return
+ }
+
+ tagIDStr := chi.URLParam(r, "tagID")
+ tagID, err := parseUUID(tagIDStr)
+ if err != nil {
+ http.Error(w, "invalid tag id", http.StatusBadRequest)
+ return
+ }
+
+ err = h.sqlc.RemoveTagFromPost(r.Context(), db.RemoveTagFromPostParams{
+ PostID: postID,
+ TagID: tagID,
+ })
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
diff --git a/backend/internal/api/tags_test.go b/backend/internal/api/tags_test.go
new file mode 100644
index 0000000..0b5ee9d
--- /dev/null
+++ b/backend/internal/api/tags_test.go
@@ -0,0 +1,206 @@
+package api
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/go-chi/chi/v5"
+ "github.com/jackc/pgx/v5/pgtype"
+ db "github.com/yeahjun/blog/backend/internal/db/generated"
+)
+
+func TestListTags_Success(t *testing.T) {
+ mock := &mockQuerier{
+ GetAllTagsFunc: func(ctx context.Context) ([]db.Tag, error) {
+ return []db.Tag{
+ {Name: "Go", Slug: "go"},
+ {Name: "Rust", Slug: "rust"},
+ }, nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ req := httptest.NewRequest(http.MethodGet, "/api/tags", nil)
+ rec := httptest.NewRecorder()
+
+ h.ListTags(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected 200, got %d", rec.Code)
+ }
+
+ var tags []db.Tag
+ json.NewDecoder(rec.Body).Decode(&tags)
+ if len(tags) != 2 {
+ t.Fatalf("expected 2 tags, got %d", len(tags))
+ }
+}
+
+func TestGetPostsByTag_Success(t *testing.T) {
+ mock := &mockQuerier{
+ GetPostByTagSlugFunc: func(ctx context.Context, arg db.GetPostByTagSlugParams) ([]db.GetPostByTagSlugRow, error) {
+ return []db.GetPostByTagSlugRow{
+ {Title: "Tagged Post", Slug: "tagged-post"},
+ }, nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ req := httptest.NewRequest(http.MethodGet, "/api/tags/go/posts", nil)
+ rctx := chi.NewRouteContext()
+ rctx.URLParams.Add("slug", "go")
+ req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
+ rec := httptest.NewRecorder()
+
+ h.GetPostsByTag(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected 200, got %d", rec.Code)
+ }
+}
+
+func TestCreateTag_Success(t *testing.T) {
+ mock := &mockQuerier{
+ CreateTagFunc: func(ctx context.Context, arg db.CreateTagParams) (db.Tag, error) {
+ return db.Tag{Name: arg.Name, Slug: arg.Slug}, nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ body := `{"name":"Go","slug":"go"}`
+ req := httptest.NewRequest(http.MethodPost, "/api/admin/tags", strings.NewReader(body))
+ rec := httptest.NewRecorder()
+
+ h.CreateTag(rec, req)
+
+ if rec.Code != http.StatusCreated {
+ t.Fatalf("expected 201, got %d", rec.Code)
+ }
+}
+
+func TestCreateTag_BadJSON(t *testing.T) {
+ h := newTestHandler(&mockQuerier{})
+ req := httptest.NewRequest(http.MethodPost, "/api/admin/tags", strings.NewReader("bad"))
+ rec := httptest.NewRecorder()
+
+ h.CreateTag(rec, req)
+
+ if rec.Code != http.StatusBadRequest {
+ t.Fatalf("expected 400, got %d", rec.Code)
+ }
+}
+
+func TestDeleteTag_Success(t *testing.T) {
+ mock := &mockQuerier{
+ DeleteTagByIDFunc: func(ctx context.Context, id pgtype.UUID) error {
+ return nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ req := httptest.NewRequest(http.MethodDelete, "/api/admin/tags/550e8400-e29b-41d4-a716-446655440000", nil)
+ rctx := chi.NewRouteContext()
+ rctx.URLParams.Add("id", "550e8400-e29b-41d4-a716-446655440000")
+ req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
+ rec := httptest.NewRecorder()
+
+ h.DeleteTag(rec, req)
+
+ if rec.Code != http.StatusNoContent {
+ t.Fatalf("expected 204, got %d", rec.Code)
+ }
+}
+
+func TestDeleteTag_BadUUID(t *testing.T) {
+ h := newTestHandler(&mockQuerier{})
+ req := httptest.NewRequest(http.MethodDelete, "/api/admin/tags/bad-uuid", nil)
+ rctx := chi.NewRouteContext()
+ rctx.URLParams.Add("id", "bad-uuid")
+ req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
+ rec := httptest.NewRecorder()
+
+ h.DeleteTag(rec, req)
+
+ if rec.Code != http.StatusBadRequest {
+ t.Fatalf("expected 400, got %d", rec.Code)
+ }
+}
+
+func TestAddTagToPost_Success(t *testing.T) {
+ mock := &mockQuerier{
+ AddTagToPostFunc: func(ctx context.Context, arg db.AddTagToPostParams) error {
+ return nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ body := `{"tag_id":"550e8400-e29b-41d4-a716-446655440001"}`
+ req := httptest.NewRequest(http.MethodPost, "/api/admin/posts/550e8400-e29b-41d4-a716-446655440000/tags", strings.NewReader(body))
+ rctx := chi.NewRouteContext()
+ rctx.URLParams.Add("id", "550e8400-e29b-41d4-a716-446655440000")
+ req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
+ rec := httptest.NewRecorder()
+
+ h.AddTagToPost(rec, req)
+
+ if rec.Code != http.StatusCreated {
+ t.Fatalf("expected 201, got %d", rec.Code)
+ }
+}
+
+func TestAddTagToPost_BadPostUUID(t *testing.T) {
+ h := newTestHandler(&mockQuerier{})
+ req := httptest.NewRequest(http.MethodPost, "/api/admin/posts/bad-uuid/tags", strings.NewReader(`{"tag_id":"550e8400-e29b-41d4-a716-446655440001"}`))
+ rctx := chi.NewRouteContext()
+ rctx.URLParams.Add("id", "bad-uuid")
+ req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
+ rec := httptest.NewRecorder()
+
+ h.AddTagToPost(rec, req)
+
+ if rec.Code != http.StatusBadRequest {
+ t.Fatalf("expected 400, got %d", rec.Code)
+ }
+}
+
+func TestRemoveTagFromPost_Success(t *testing.T) {
+ mock := &mockQuerier{
+ RemoveTagFromPostFunc: func(ctx context.Context, arg db.RemoveTagFromPostParams) error {
+ return nil
+ },
+ }
+
+ h := newTestHandler(mock)
+ req := httptest.NewRequest(http.MethodDelete, "/api/admin/posts/550e8400-e29b-41d4-a716-446655440000/tags/550e8400-e29b-41d4-a716-446655440001", nil)
+ rctx := chi.NewRouteContext()
+ rctx.URLParams.Add("id", "550e8400-e29b-41d4-a716-446655440000")
+ rctx.URLParams.Add("tagID", "550e8400-e29b-41d4-a716-446655440001")
+ req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
+ rec := httptest.NewRecorder()
+
+ h.RemoveTagFromPost(rec, req)
+
+ if rec.Code != http.StatusNoContent {
+ t.Fatalf("expected 204, got %d", rec.Code)
+ }
+}
+
+func TestRemoveTagFromPost_BadPostUUID(t *testing.T) {
+ h := newTestHandler(&mockQuerier{})
+ req := httptest.NewRequest(http.MethodDelete, "/api/admin/posts/bad-uuid/tags/550e8400-e29b-41d4-a716-446655440001", nil)
+ rctx := chi.NewRouteContext()
+ rctx.URLParams.Add("id", "bad-uuid")
+ rctx.URLParams.Add("tagID", "550e8400-e29b-41d4-a716-446655440001")
+ req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
+ rec := httptest.NewRecorder()
+
+ h.RemoveTagFromPost(rec, req)
+
+ if rec.Code != http.StatusBadRequest {
+ t.Fatalf("expected 400, got %d", rec.Code)
+ }
+}
diff --git a/backend/internal/auth/bcrypt.go b/backend/internal/auth/bcrypt.go
new file mode 100644
index 0000000..2e24112
--- /dev/null
+++ b/backend/internal/auth/bcrypt.go
@@ -0,0 +1,15 @@
+package auth
+
+import (
+ "golang.org/x/crypto/bcrypt"
+)
+
+func HashPassword(password string) (string, error) {
+ bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ return string(bytes), err
+}
+
+func ComparePassword(hash, password string) bool {
+ err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
+ return err == nil
+}
diff --git a/backend/internal/auth/bcrypt_test.go b/backend/internal/auth/bcrypt_test.go
new file mode 100644
index 0000000..7e46beb
--- /dev/null
+++ b/backend/internal/auth/bcrypt_test.go
@@ -0,0 +1,20 @@
+package auth
+
+import "testing"
+
+func TestHashAndComparePassword(t *testing.T) {
+ hash, err := HashPassword("mysecretpassword")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !ComparePassword(hash, "mysecretpassword") {
+ t.Fatal("expected password to match")
+ }
+}
+
+func TestComparePassword_Wrong(t *testing.T) {
+ hash, _ := HashPassword("correctpassword")
+ if ComparePassword(hash, "wrongpassword") {
+ t.Fatal("expected password not to match")
+ }
+}
diff --git a/backend/internal/auth/jwt.go b/backend/internal/auth/jwt.go
new file mode 100644
index 0000000..912ba10
--- /dev/null
+++ b/backend/internal/auth/jwt.go
@@ -0,0 +1,62 @@
+package auth
+
+import (
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "time"
+
+ "github.com/golang-jwt/jwt/v5"
+)
+
+type TokenConfig struct {
+ Secret string
+ AccessTokenExpiry time.Duration
+ RefreshTokenExpiry time.Duration
+}
+
+func GenerateAccessToken(authorID string, cfg TokenConfig) (string, error) {
+ claims := jwt.MapClaims{
+ "sub": authorID,
+ "iat": time.Now().Unix(),
+ "exp": time.Now().Add(cfg.AccessTokenExpiry).Unix(),
+ }
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ return token.SignedString([]byte(cfg.Secret))
+}
+
+func ValidateAccessToken(tokenStr, secret string) (string, error) {
+ token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
+ if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
+ return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
+ }
+ return []byte(secret), nil
+ })
+ if err != nil {
+ return "", err
+ }
+ claims, ok := token.Claims.(jwt.MapClaims)
+ if !ok {
+ return "", fmt.Errorf("invalid claims")
+ }
+ sub, err := claims.GetSubject()
+ if err != nil {
+ return "", err
+ }
+ return sub, nil
+}
+
+func GenerateRefreshToken() (string, error) {
+ bytes := make([]byte, 32)
+ _, err := rand.Read(bytes)
+ if err != nil {
+ return "", err
+ }
+ return hex.EncodeToString(bytes), nil
+}
+
+func HashToken(token string) string {
+ hash := sha256.Sum256([]byte(token))
+ return hex.EncodeToString(hash[:])
+}
diff --git a/backend/internal/auth/jwt_test.go b/backend/internal/auth/jwt_test.go
new file mode 100644
index 0000000..3112a45
--- /dev/null
+++ b/backend/internal/auth/jwt_test.go
@@ -0,0 +1,102 @@
+package auth
+
+import (
+ "testing"
+ "time"
+)
+
+func TestGenerateAccessToken(t *testing.T) {
+ cfg := TokenConfig{
+ Secret: "test-secret",
+ AccessTokenExpiry: 15 * time.Minute,
+ }
+
+ token, err := GenerateAccessToken("author-123", cfg)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if token == "" {
+ t.Fatal("expected non-empty token")
+ }
+
+ sub, err := ValidateAccessToken(token, cfg.Secret)
+ if err != nil {
+ t.Fatalf("token should be valid: %v", err)
+ }
+ if sub != "author-123" {
+ t.Fatalf("expected subject author-123, got %s", sub)
+ }
+}
+
+func TestValidateAccessToken(t *testing.T) {
+ cfg := TokenConfig{
+ Secret: "test-secret",
+ AccessTokenExpiry: 15 * time.Minute,
+ }
+
+ token, _ := GenerateAccessToken("author-456", cfg)
+ sub, err := ValidateAccessToken(token, cfg.Secret)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if sub != "author-456" {
+ t.Fatalf("expected author-456, got %s", sub)
+ }
+}
+
+func TestValidateAccessToken_Expired(t *testing.T) {
+ cfg := TokenConfig{
+ Secret: "test-secret",
+ AccessTokenExpiry: -1 * time.Second,
+ }
+
+ token, _ := GenerateAccessToken("author-789", cfg)
+ _, err := ValidateAccessToken(token, cfg.Secret)
+ if err == nil {
+ t.Fatal("expected error for expired token")
+ }
+}
+
+func TestValidateAccessToken_WrongSecret(t *testing.T) {
+ cfg := TokenConfig{
+ Secret: "correct-secret",
+ AccessTokenExpiry: 15 * time.Minute,
+ }
+
+ token, _ := GenerateAccessToken("author-abc", cfg)
+ _, err := ValidateAccessToken(token, "wrong-secret")
+ if err == nil {
+ t.Fatal("expected error for wrong secret")
+ }
+}
+
+func TestHashToken(t *testing.T) {
+ hash1 := HashToken("my-token")
+ hash2 := HashToken("my-token")
+ if hash1 != hash2 {
+ t.Fatal("hash should be deterministic")
+ }
+ if len(hash1) != 64 {
+ t.Fatalf("expected 64-char hex string, got len %d", len(hash1))
+ }
+
+ different := HashToken("other-token")
+ if hash1 == different {
+ t.Fatal("different inputs should produce different hashes")
+ }
+}
+
+func TestGenerateRefreshToken(t *testing.T) {
+ token1, err := GenerateRefreshToken()
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if len(token1) != 64 {
+ t.Fatalf("expected 64-char hex string, got len %d", len(token1))
+ }
+
+ token2, _ := GenerateRefreshToken()
+ if token1 == token2 {
+ t.Fatal("tokens should be unique")
+ }
+}
diff --git a/backend/internal/auth/middleware.go b/backend/internal/auth/middleware.go
new file mode 100644
index 0000000..71b8678
--- /dev/null
+++ b/backend/internal/auth/middleware.go
@@ -0,0 +1,29 @@
+package auth
+
+import (
+ "context"
+ "net/http"
+)
+
+type contextKey string
+
+const AuthorIDKey contextKey = "authorID"
+
+func JWTMiddleware(secret string) func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ cookie, err := r.Cookie("access_token")
+ if err != nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+ authorID, err := ValidateAccessToken(cookie.Value, secret)
+ if err != nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+ ctx := context.WithValue(r.Context(), AuthorIDKey, authorID)
+ next.ServeHTTP(w, r.WithContext(ctx))
+ })
+ }
+}
diff --git a/backend/internal/auth/middleware_test.go b/backend/internal/auth/middleware_test.go
new file mode 100644
index 0000000..9bbda4a
--- /dev/null
+++ b/backend/internal/auth/middleware_test.go
@@ -0,0 +1,65 @@
+package auth
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+)
+
+func TestJWTMiddleware_ValidToken(t *testing.T) {
+ cfg := TokenConfig{
+ Secret: "test-secret",
+ AccessTokenExpiry: 15 * time.Minute,
+ }
+ token, _ := GenerateAccessToken("author-mid", cfg)
+
+ var gotAuthorID string
+ next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ gotAuthorID, _ = r.Context().Value(AuthorIDKey).(string)
+ w.WriteHeader(http.StatusOK)
+ })
+
+ handler := JWTMiddleware(cfg.Secret)(next)
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ req.AddCookie(&http.Cookie{Name: "access_token", Value: token})
+ rec := httptest.NewRecorder()
+
+ handler.ServeHTTP(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected 200, got %d", rec.Code)
+ }
+ if gotAuthorID != "author-mid" {
+ t.Fatalf("expected author-mid in context, got %s", gotAuthorID)
+ }
+}
+
+func TestJWTMiddleware_NoCookie(t *testing.T) {
+ handler := JWTMiddleware("secret")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("next handler should not be called")
+ }))
+
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ rec := httptest.NewRecorder()
+ handler.ServeHTTP(rec, req)
+
+ if rec.Code != http.StatusUnauthorized {
+ t.Fatalf("expected 401, got %d", rec.Code)
+ }
+}
+
+func TestJWTMiddleware_InvalidToken(t *testing.T) {
+ handler := JWTMiddleware("secret")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("next handler should not be called")
+ }))
+
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ req.AddCookie(&http.Cookie{Name: "access_token", Value: "invalid.token.here"})
+ rec := httptest.NewRecorder()
+ handler.ServeHTTP(rec, req)
+
+ if rec.Code != http.StatusUnauthorized {
+ t.Fatalf("expected 401, got %d", rec.Code)
+ }
+}
diff --git a/backend/internal/auth/ratelimit.go b/backend/internal/auth/ratelimit.go
new file mode 100644
index 0000000..48be8a1
--- /dev/null
+++ b/backend/internal/auth/ratelimit.go
@@ -0,0 +1,67 @@
+package auth
+
+import (
+ "net"
+ "net/http"
+ "strings"
+ "sync"
+ "time"
+)
+
+type attempt struct {
+ count int
+ windowStart time.Time
+}
+
+type RateLimiter struct {
+ mu sync.Mutex
+ attempts map[string]*attempt
+ max int
+ window time.Duration
+}
+
+func NewRateLimiter(max int, window time.Duration) *RateLimiter {
+ return &RateLimiter{
+ attempts: make(map[string]*attempt),
+ max: max,
+ window: window,
+ }
+}
+
+func clientIP(r *http.Request) string {
+ if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
+ // First IP in the chain is the original client
+ if ip := strings.TrimSpace(strings.SplitN(xff, ",", 2)[0]); ip != "" {
+ return ip
+ }
+ }
+ ip, _, err := net.SplitHostPort(r.RemoteAddr)
+ if err != nil {
+ return r.RemoteAddr
+ }
+ return ip
+}
+
+func (rl *RateLimiter) Limit(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ip := clientIP(r)
+
+ rl.mu.Lock()
+ a, exists := rl.attempts[ip]
+ if !exists || time.Since(a.windowStart) > rl.window {
+ rl.attempts[ip] = &attempt{count: 1, windowStart: time.Now()}
+ rl.mu.Unlock()
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ a.count++
+ if a.count > rl.max {
+ rl.mu.Unlock()
+ http.Error(w, "too many requests", http.StatusTooManyRequests)
+ return
+ }
+ rl.mu.Unlock()
+ next.ServeHTTP(w, r)
+ })
+}
diff --git a/backend/internal/auth/ratelimit_test.go b/backend/internal/auth/ratelimit_test.go
new file mode 100644
index 0000000..ac1832a
--- /dev/null
+++ b/backend/internal/auth/ratelimit_test.go
@@ -0,0 +1,103 @@
+package auth
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+)
+
+func TestRateLimiter_AllowsUnderLimit(t *testing.T) {
+ rl := NewRateLimiter(3, time.Minute)
+ handler := rl.Limit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ }))
+
+ for i := 0; i < 3; i++ {
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ req.RemoteAddr = "1.2.3.4:1234"
+ rec := httptest.NewRecorder()
+ handler.ServeHTTP(rec, req)
+ if rec.Code != http.StatusOK {
+ t.Fatalf("request %d: expected 200, got %d", i+1, rec.Code)
+ }
+ }
+}
+
+func TestRateLimiter_BlocksOverLimit(t *testing.T) {
+ rl := NewRateLimiter(2, time.Minute)
+ handler := rl.Limit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ }))
+
+ for i := 0; i < 2; i++ {
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ req.RemoteAddr = "1.2.3.4:1234"
+ rec := httptest.NewRecorder()
+ handler.ServeHTTP(rec, req)
+ }
+
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ req.RemoteAddr = "1.2.3.4:1234"
+ rec := httptest.NewRecorder()
+ handler.ServeHTTP(rec, req)
+ if rec.Code != http.StatusTooManyRequests {
+ t.Fatalf("expected 429, got %d", rec.Code)
+ }
+}
+
+func TestRateLimiter_WindowResets(t *testing.T) {
+ rl := NewRateLimiter(1, 50*time.Millisecond)
+ handler := rl.Limit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ }))
+
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ req.RemoteAddr = "1.2.3.4:1234"
+ rec := httptest.NewRecorder()
+ handler.ServeHTTP(rec, req)
+ if rec.Code != http.StatusOK {
+ t.Fatalf("first request: expected 200, got %d", rec.Code)
+ }
+
+ // Second request should be blocked
+ req = httptest.NewRequest(http.MethodGet, "/", nil)
+ req.RemoteAddr = "1.2.3.4:1234"
+ rec = httptest.NewRecorder()
+ handler.ServeHTTP(rec, req)
+ if rec.Code != http.StatusTooManyRequests {
+ t.Fatalf("second request: expected 429, got %d", rec.Code)
+ }
+
+ // Wait for window to expire
+ time.Sleep(60 * time.Millisecond)
+
+ req = httptest.NewRequest(http.MethodGet, "/", nil)
+ req.RemoteAddr = "1.2.3.4:1234"
+ rec = httptest.NewRecorder()
+ handler.ServeHTTP(rec, req)
+ if rec.Code != http.StatusOK {
+ t.Fatalf("after window reset: expected 200, got %d", rec.Code)
+ }
+}
+
+func TestClientIP_XForwardedFor(t *testing.T) {
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ req.Header.Set("X-Forwarded-For", "10.0.0.1, 10.0.0.2")
+ req.RemoteAddr = "192.168.1.1:9999"
+
+ ip := clientIP(req)
+ if ip != "10.0.0.1" {
+ t.Fatalf("expected 10.0.0.1, got %s", ip)
+ }
+}
+
+func TestClientIP_RemoteAddr(t *testing.T) {
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ req.RemoteAddr = "192.168.1.1:9999"
+
+ ip := clientIP(req)
+ if ip != "192.168.1.1" {
+ t.Fatalf("expected 192.168.1.1, got %s", ip)
+ }
+}
diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go
new file mode 100644
index 0000000..7fa5fc7
--- /dev/null
+++ b/backend/internal/config/config.go
@@ -0,0 +1,36 @@
+package config
+
+import (
+ "fmt"
+ "os"
+)
+
+type Config struct {
+ DatabaseURL string
+ JWTSecret string
+ Port string
+ AllowedOrigin string
+}
+
+func Load() (*Config, error) {
+ cfg := &Config{
+ DatabaseURL: os.Getenv("DATABASE_URL"),
+ JWTSecret: os.Getenv("JWT_SECRET"),
+ Port: os.Getenv("PORT"),
+ AllowedOrigin: os.Getenv("ALLOWED_ORIGIN"),
+ }
+
+ if cfg.DatabaseURL == "" {
+ return nil, fmt.Errorf("DATABASE_URL is required.")
+ }
+ if cfg.JWTSecret == "" {
+ return nil, fmt.Errorf("JWT_SECRET is required.")
+ }
+ if cfg.Port == "" {
+ cfg.Port = "8080"
+ }
+ if cfg.AllowedOrigin == "" {
+ cfg.AllowedOrigin = "http://localhost:4321"
+ }
+ return cfg, nil
+}
diff --git a/backend/internal/config/config_test.go b/backend/internal/config/config_test.go
new file mode 100644
index 0000000..6cc0e7f
--- /dev/null
+++ b/backend/internal/config/config_test.go
@@ -0,0 +1,68 @@
+package config
+
+import (
+ "os"
+ "testing"
+)
+
+func TestLoad_AllSet(t *testing.T) {
+ t.Setenv("DATABASE_URL", "postgres://localhost/test")
+ t.Setenv("JWT_SECRET", "supersecret")
+ t.Setenv("PORT", "3000")
+ t.Setenv("ALLOWED_ORIGIN", "https://example.com")
+
+ cfg, err := Load()
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if cfg.DatabaseURL != "postgres://localhost/test" {
+ t.Fatalf("expected DATABASE_URL=postgres://localhost/test, got %s", cfg.DatabaseURL)
+ }
+ if cfg.JWTSecret != "supersecret" {
+ t.Fatalf("expected JWT_SECRET=supersecret, got %s", cfg.JWTSecret)
+ }
+ if cfg.Port != "3000" {
+ t.Fatalf("expected PORT=3000, got %s", cfg.Port)
+ }
+ if cfg.AllowedOrigin != "https://example.com" {
+ t.Fatalf("expected ALLOWED_ORIGIN=https://example.com, got %s", cfg.AllowedOrigin)
+ }
+}
+
+func TestLoad_MissingDatabaseURL(t *testing.T) {
+ os.Unsetenv("DATABASE_URL")
+ t.Setenv("JWT_SECRET", "secret")
+
+ _, err := Load()
+ if err == nil {
+ t.Fatal("expected error for missing DATABASE_URL")
+ }
+}
+
+func TestLoad_MissingJWTSecret(t *testing.T) {
+ t.Setenv("DATABASE_URL", "postgres://localhost/test")
+ os.Unsetenv("JWT_SECRET")
+
+ _, err := Load()
+ if err == nil {
+ t.Fatal("expected error for missing JWT_SECRET")
+ }
+}
+
+func TestLoad_Defaults(t *testing.T) {
+ t.Setenv("DATABASE_URL", "postgres://localhost/test")
+ t.Setenv("JWT_SECRET", "secret")
+ os.Unsetenv("PORT")
+ os.Unsetenv("ALLOWED_ORIGIN")
+
+ cfg, err := Load()
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if cfg.Port != "8080" {
+ t.Fatalf("expected default PORT=8080, got %s", cfg.Port)
+ }
+ if cfg.AllowedOrigin != "http://localhost:4321" {
+ t.Fatalf("expected default ALLOWED_ORIGIN=http://localhost:4321, got %s", cfg.AllowedOrigin)
+ }
+}
diff --git a/backend/internal/db/generated/auth.sql.go b/backend/internal/db/generated/auth.sql.go
new file mode 100644
index 0000000..af05c04
--- /dev/null
+++ b/backend/internal/db/generated/auth.sql.go
@@ -0,0 +1,113 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.30.0
+// source: auth.sql
+
+package db
+
+import (
+ "context"
+
+ "github.com/jackc/pgx/v5/pgtype"
+)
+
+const createAuthor = `-- name: CreateAuthor :one
+INSERT INTO authors (username, password_hash)
+VALUES ($1, $2) RETURNING id, username, password_hash, created_at
+`
+
+type CreateAuthorParams struct {
+ Username string `json:"username"`
+ PasswordHash string `json:"password_hash"`
+}
+
+func (q *Queries) CreateAuthor(ctx context.Context, arg CreateAuthorParams) (Author, error) {
+ row := q.db.QueryRow(ctx, createAuthor, arg.Username, arg.PasswordHash)
+ var i Author
+ err := row.Scan(
+ &i.ID,
+ &i.Username,
+ &i.PasswordHash,
+ &i.CreatedAt,
+ )
+ return i, err
+}
+
+const createRefreshToken = `-- name: CreateRefreshToken :one
+INSERT INTO refresh_tokens (author_id, token_hash, expires_at)
+VALUES ($1, $2, $3) RETURNING id, author_id, token_hash, expires_at, created_at
+`
+
+type CreateRefreshTokenParams struct {
+ AuthorID pgtype.UUID `json:"author_id"`
+ TokenHash string `json:"token_hash"`
+ ExpiresAt pgtype.Timestamptz `json:"expires_at"`
+}
+
+func (q *Queries) CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error) {
+ row := q.db.QueryRow(ctx, createRefreshToken, arg.AuthorID, arg.TokenHash, arg.ExpiresAt)
+ var i RefreshToken
+ err := row.Scan(
+ &i.ID,
+ &i.AuthorID,
+ &i.TokenHash,
+ &i.ExpiresAt,
+ &i.CreatedAt,
+ )
+ return i, err
+}
+
+const deleteRefreshToken = `-- name: DeleteRefreshToken :exec
+DELETE FROM refresh_tokens
+WHERE token_hash = $1
+`
+
+func (q *Queries) DeleteRefreshToken(ctx context.Context, tokenHash string) error {
+ _, err := q.db.Exec(ctx, deleteRefreshToken, tokenHash)
+ return err
+}
+
+const deleteRefreshTokenByAuthor = `-- name: DeleteRefreshTokenByAuthor :exec
+DELETE FROM refresh_tokens
+WHERE author_id = $1
+`
+
+func (q *Queries) DeleteRefreshTokenByAuthor(ctx context.Context, authorID pgtype.UUID) error {
+ _, err := q.db.Exec(ctx, deleteRefreshTokenByAuthor, authorID)
+ return err
+}
+
+const getAuthorByUsername = `-- name: GetAuthorByUsername :one
+SELECT id, username, password_hash, created_at FROM authors
+WHERE username = $1 LIMIT 1
+`
+
+func (q *Queries) GetAuthorByUsername(ctx context.Context, username string) (Author, error) {
+ row := q.db.QueryRow(ctx, getAuthorByUsername, username)
+ var i Author
+ err := row.Scan(
+ &i.ID,
+ &i.Username,
+ &i.PasswordHash,
+ &i.CreatedAt,
+ )
+ return i, err
+}
+
+const getRefreshToken = `-- name: GetRefreshToken :one
+SELECT id, author_id, token_hash, expires_at, created_at FROM refresh_tokens
+WHERE token_hash = $1 LIMIT 1
+`
+
+func (q *Queries) GetRefreshToken(ctx context.Context, tokenHash string) (RefreshToken, error) {
+ row := q.db.QueryRow(ctx, getRefreshToken, tokenHash)
+ var i RefreshToken
+ err := row.Scan(
+ &i.ID,
+ &i.AuthorID,
+ &i.TokenHash,
+ &i.ExpiresAt,
+ &i.CreatedAt,
+ )
+ return i, err
+}
diff --git a/backend/internal/db/generated/db.go b/backend/internal/db/generated/db.go
new file mode 100644
index 0000000..9d485b5
--- /dev/null
+++ b/backend/internal/db/generated/db.go
@@ -0,0 +1,32 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.30.0
+
+package db
+
+import (
+ "context"
+
+ "github.com/jackc/pgx/v5"
+ "github.com/jackc/pgx/v5/pgconn"
+)
+
+type DBTX interface {
+ Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
+ Query(context.Context, string, ...interface{}) (pgx.Rows, error)
+ QueryRow(context.Context, string, ...interface{}) pgx.Row
+}
+
+func New(db DBTX) *Queries {
+ return &Queries{db: db}
+}
+
+type Queries struct {
+ db DBTX
+}
+
+func (q *Queries) WithTx(tx pgx.Tx) *Queries {
+ return &Queries{
+ db: tx,
+ }
+}
diff --git a/backend/internal/db/generated/models.go b/backend/internal/db/generated/models.go
new file mode 100644
index 0000000..8d5bf89
--- /dev/null
+++ b/backend/internal/db/generated/models.go
@@ -0,0 +1,47 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.30.0
+
+package db
+
+import (
+ "github.com/jackc/pgx/v5/pgtype"
+)
+
+type Author struct {
+ ID pgtype.UUID `json:"id"`
+ Username string `json:"username"`
+ PasswordHash string `json:"password_hash"`
+ CreatedAt pgtype.Timestamptz `json:"created_at"`
+}
+
+type Post struct {
+ ID pgtype.UUID `json:"id"`
+ Title string `json:"title"`
+ Slug string `json:"slug"`
+ Content string `json:"content"`
+ Excerpt *string `json:"excerpt"`
+ Status string `json:"status"`
+ PublishedAt pgtype.Timestamptz `json:"published_at"`
+ CreatedAt pgtype.Timestamptz `json:"created_at"`
+ UpdatedAt pgtype.Timestamptz `json:"updated_at"`
+}
+
+type PostTag struct {
+ PostID pgtype.UUID `json:"post_id"`
+ TagID pgtype.UUID `json:"tag_id"`
+}
+
+type RefreshToken struct {
+ ID pgtype.UUID `json:"id"`
+ AuthorID pgtype.UUID `json:"author_id"`
+ TokenHash string `json:"token_hash"`
+ ExpiresAt pgtype.Timestamptz `json:"expires_at"`
+ CreatedAt pgtype.Timestamptz `json:"created_at"`
+}
+
+type Tag struct {
+ ID pgtype.UUID `json:"id"`
+ Name string `json:"name"`
+ Slug string `json:"slug"`
+}
diff --git a/backend/internal/db/generated/posts.sql.go b/backend/internal/db/generated/posts.sql.go
new file mode 100644
index 0000000..54e3e06
--- /dev/null
+++ b/backend/internal/db/generated/posts.sql.go
@@ -0,0 +1,247 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.30.0
+// source: posts.sql
+
+package db
+
+import (
+ "context"
+
+ "github.com/jackc/pgx/v5/pgtype"
+)
+
+const createPost = `-- name: CreatePost :one
+INSERT INTO posts (title, slug, content, status)
+VALUES ($1, $2, $3, $4) RETURNING id, title, slug, content, excerpt, status, published_at, created_at, updated_at
+`
+
+type CreatePostParams struct {
+ Title string `json:"title"`
+ Slug string `json:"slug"`
+ Content string `json:"content"`
+ Status string `json:"status"`
+}
+
+func (q *Queries) CreatePost(ctx context.Context, arg CreatePostParams) (Post, error) {
+ row := q.db.QueryRow(ctx, createPost,
+ arg.Title,
+ arg.Slug,
+ arg.Content,
+ arg.Status,
+ )
+ var i Post
+ err := row.Scan(
+ &i.ID,
+ &i.Title,
+ &i.Slug,
+ &i.Content,
+ &i.Excerpt,
+ &i.Status,
+ &i.PublishedAt,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ )
+ return i, err
+}
+
+const deletePostByID = `-- name: DeletePostByID :exec
+DELETE FROM posts
+WHERE id = $1
+`
+
+func (q *Queries) DeletePostByID(ctx context.Context, id pgtype.UUID) error {
+ _, err := q.db.Exec(ctx, deletePostByID, id)
+ return err
+}
+
+const getPostBySlug = `-- name: GetPostBySlug :one
+SELECT id, title, slug, content, excerpt, status, published_at, created_at, updated_at FROM posts
+WHERE slug = $1 LIMIT 1
+`
+
+func (q *Queries) GetPostBySlug(ctx context.Context, slug string) (Post, error) {
+ row := q.db.QueryRow(ctx, getPostBySlug, slug)
+ var i Post
+ err := row.Scan(
+ &i.ID,
+ &i.Title,
+ &i.Slug,
+ &i.Content,
+ &i.Excerpt,
+ &i.Status,
+ &i.PublishedAt,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ )
+ return i, err
+}
+
+const getPostTags = `-- name: GetPostTags :many
+SELECT id, name, slug, post_id, tag_id FROM tags
+JOIN post_tags ON post_tags.tag_id = tags.id
+WHERE post_tags.post_id = $1
+`
+
+type GetPostTagsRow struct {
+ ID pgtype.UUID `json:"id"`
+ Name string `json:"name"`
+ Slug string `json:"slug"`
+ PostID pgtype.UUID `json:"post_id"`
+ TagID pgtype.UUID `json:"tag_id"`
+}
+
+func (q *Queries) GetPostTags(ctx context.Context, postID pgtype.UUID) ([]GetPostTagsRow, error) {
+ rows, err := q.db.Query(ctx, getPostTags, postID)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []GetPostTagsRow
+ for rows.Next() {
+ var i GetPostTagsRow
+ if err := rows.Scan(
+ &i.ID,
+ &i.Name,
+ &i.Slug,
+ &i.PostID,
+ &i.TagID,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
+const getPostsAdmin = `-- name: GetPostsAdmin :many
+SELECT id, title, slug, content, excerpt, status, published_at, created_at, updated_at FROM posts
+ORDER BY created_at DESC
+LIMIT $1 OFFSET $2
+`
+
+type GetPostsAdminParams struct {
+ Limit int32 `json:"limit"`
+ Offset int32 `json:"offset"`
+}
+
+func (q *Queries) GetPostsAdmin(ctx context.Context, arg GetPostsAdminParams) ([]Post, error) {
+ rows, err := q.db.Query(ctx, getPostsAdmin, arg.Limit, arg.Offset)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []Post
+ for rows.Next() {
+ var i Post
+ if err := rows.Scan(
+ &i.ID,
+ &i.Title,
+ &i.Slug,
+ &i.Content,
+ &i.Excerpt,
+ &i.Status,
+ &i.PublishedAt,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
+const getPublishedPosts = `-- name: GetPublishedPosts :many
+SELECT id, title, slug, content, excerpt, status, published_at, created_at, updated_at FROM posts
+WHERE status = 'published'
+ORDER BY published_at DESC
+LIMIT $1 OFFSET $2
+`
+
+type GetPublishedPostsParams struct {
+ Limit int32 `json:"limit"`
+ Offset int32 `json:"offset"`
+}
+
+func (q *Queries) GetPublishedPosts(ctx context.Context, arg GetPublishedPostsParams) ([]Post, error) {
+ rows, err := q.db.Query(ctx, getPublishedPosts, arg.Limit, arg.Offset)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []Post
+ for rows.Next() {
+ var i Post
+ if err := rows.Scan(
+ &i.ID,
+ &i.Title,
+ &i.Slug,
+ &i.Content,
+ &i.Excerpt,
+ &i.Status,
+ &i.PublishedAt,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
+const updatePostByID = `-- name: UpdatePostByID :one
+UPDATE posts SET
+ title = $2,
+ slug = $3,
+ content = $4,
+ excerpt = $5,
+ status = $6,
+ published_at = $7
+WHERE id = $1 RETURNING id, title, slug, content, excerpt, status, published_at, created_at, updated_at
+`
+
+type UpdatePostByIDParams struct {
+ ID pgtype.UUID `json:"id"`
+ Title string `json:"title"`
+ Slug string `json:"slug"`
+ Content string `json:"content"`
+ Excerpt *string `json:"excerpt"`
+ Status string `json:"status"`
+ PublishedAt pgtype.Timestamptz `json:"published_at"`
+}
+
+func (q *Queries) UpdatePostByID(ctx context.Context, arg UpdatePostByIDParams) (Post, error) {
+ row := q.db.QueryRow(ctx, updatePostByID,
+ arg.ID,
+ arg.Title,
+ arg.Slug,
+ arg.Content,
+ arg.Excerpt,
+ arg.Status,
+ arg.PublishedAt,
+ )
+ var i Post
+ err := row.Scan(
+ &i.ID,
+ &i.Title,
+ &i.Slug,
+ &i.Content,
+ &i.Excerpt,
+ &i.Status,
+ &i.PublishedAt,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ )
+ return i, err
+}
diff --git a/backend/internal/db/generated/querier.go b/backend/internal/db/generated/querier.go
new file mode 100644
index 0000000..6f00c1c
--- /dev/null
+++ b/backend/internal/db/generated/querier.go
@@ -0,0 +1,35 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.30.0
+
+package db
+
+import (
+ "context"
+
+ "github.com/jackc/pgx/v5/pgtype"
+)
+
+type Querier interface {
+ AddTagToPost(ctx context.Context, arg AddTagToPostParams) error
+ CreateAuthor(ctx context.Context, arg CreateAuthorParams) (Author, error)
+ CreatePost(ctx context.Context, arg CreatePostParams) (Post, error)
+ CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error)
+ CreateTag(ctx context.Context, arg CreateTagParams) (Tag, error)
+ DeletePostByID(ctx context.Context, id pgtype.UUID) error
+ DeleteRefreshToken(ctx context.Context, tokenHash string) error
+ DeleteRefreshTokenByAuthor(ctx context.Context, authorID pgtype.UUID) error
+ DeleteTagByID(ctx context.Context, id pgtype.UUID) error
+ GetAllTags(ctx context.Context) ([]Tag, error)
+ GetAuthorByUsername(ctx context.Context, username string) (Author, error)
+ GetPostBySlug(ctx context.Context, slug string) (Post, error)
+ GetPostByTagSlug(ctx context.Context, arg GetPostByTagSlugParams) ([]GetPostByTagSlugRow, error)
+ GetPostTags(ctx context.Context, postID pgtype.UUID) ([]GetPostTagsRow, error)
+ GetPostsAdmin(ctx context.Context, arg GetPostsAdminParams) ([]Post, error)
+ GetPublishedPosts(ctx context.Context, arg GetPublishedPostsParams) ([]Post, error)
+ GetRefreshToken(ctx context.Context, tokenHash string) (RefreshToken, error)
+ RemoveTagFromPost(ctx context.Context, arg RemoveTagFromPostParams) error
+ UpdatePostByID(ctx context.Context, arg UpdatePostByIDParams) (Post, error)
+}
+
+var _ Querier = (*Queries)(nil)
diff --git a/backend/internal/db/generated/tags.sql.go b/backend/internal/db/generated/tags.sql.go
new file mode 100644
index 0000000..1d24ca4
--- /dev/null
+++ b/backend/internal/db/generated/tags.sql.go
@@ -0,0 +1,160 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.30.0
+// source: tags.sql
+
+package db
+
+import (
+ "context"
+
+ "github.com/jackc/pgx/v5/pgtype"
+)
+
+const addTagToPost = `-- name: AddTagToPost :exec
+INSERT INTO post_tags (post_id, tag_id)
+VALUES ($1, $2)
+`
+
+type AddTagToPostParams struct {
+ PostID pgtype.UUID `json:"post_id"`
+ TagID pgtype.UUID `json:"tag_id"`
+}
+
+func (q *Queries) AddTagToPost(ctx context.Context, arg AddTagToPostParams) error {
+ _, err := q.db.Exec(ctx, addTagToPost, arg.PostID, arg.TagID)
+ return err
+}
+
+const createTag = `-- name: CreateTag :one
+INSERT INTO tags (name, slug)
+VALUES ($1, $2) RETURNING id, name, slug
+`
+
+type CreateTagParams struct {
+ Name string `json:"name"`
+ Slug string `json:"slug"`
+}
+
+func (q *Queries) CreateTag(ctx context.Context, arg CreateTagParams) (Tag, error) {
+ row := q.db.QueryRow(ctx, createTag, arg.Name, arg.Slug)
+ var i Tag
+ err := row.Scan(&i.ID, &i.Name, &i.Slug)
+ return i, err
+}
+
+const deleteTagByID = `-- name: DeleteTagByID :exec
+DELETE FROM tags
+WHERE id = $1
+`
+
+func (q *Queries) DeleteTagByID(ctx context.Context, id pgtype.UUID) error {
+ _, err := q.db.Exec(ctx, deleteTagByID, id)
+ return err
+}
+
+const getAllTags = `-- name: GetAllTags :many
+SELECT id, name, slug FROM tags
+`
+
+func (q *Queries) GetAllTags(ctx context.Context) ([]Tag, error) {
+ rows, err := q.db.Query(ctx, getAllTags)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []Tag
+ for rows.Next() {
+ var i Tag
+ if err := rows.Scan(&i.ID, &i.Name, &i.Slug); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
+const getPostByTagSlug = `-- name: GetPostByTagSlug :many
+SELECT posts.id, title, posts.slug, content, excerpt, status, published_at, created_at, updated_at, post_id, tag_id, tags.id, name, tags.slug FROM posts
+JOIN post_tags ON post_tags.post_id = posts.id
+JOIN tags ON tags.id = post_tags.tag_id
+WHERE tags.slug = $1
+ORDER BY posts.published_at DESC
+LIMIT $2 OFFSET $3
+`
+
+type GetPostByTagSlugParams struct {
+ Slug string `json:"slug"`
+ Limit int32 `json:"limit"`
+ Offset int32 `json:"offset"`
+}
+
+type GetPostByTagSlugRow struct {
+ ID pgtype.UUID `json:"id"`
+ Title string `json:"title"`
+ Slug string `json:"slug"`
+ Content string `json:"content"`
+ Excerpt *string `json:"excerpt"`
+ Status string `json:"status"`
+ PublishedAt pgtype.Timestamptz `json:"published_at"`
+ CreatedAt pgtype.Timestamptz `json:"created_at"`
+ UpdatedAt pgtype.Timestamptz `json:"updated_at"`
+ PostID pgtype.UUID `json:"post_id"`
+ TagID pgtype.UUID `json:"tag_id"`
+ ID_2 pgtype.UUID `json:"id_2"`
+ Name string `json:"name"`
+ Slug_2 string `json:"slug_2"`
+}
+
+func (q *Queries) GetPostByTagSlug(ctx context.Context, arg GetPostByTagSlugParams) ([]GetPostByTagSlugRow, error) {
+ rows, err := q.db.Query(ctx, getPostByTagSlug, arg.Slug, arg.Limit, arg.Offset)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []GetPostByTagSlugRow
+ for rows.Next() {
+ var i GetPostByTagSlugRow
+ if err := rows.Scan(
+ &i.ID,
+ &i.Title,
+ &i.Slug,
+ &i.Content,
+ &i.Excerpt,
+ &i.Status,
+ &i.PublishedAt,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ &i.PostID,
+ &i.TagID,
+ &i.ID_2,
+ &i.Name,
+ &i.Slug_2,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
+const removeTagFromPost = `-- name: RemoveTagFromPost :exec
+DELETE FROM post_tags
+WHERE post_id = $1 AND tag_id = $2
+`
+
+type RemoveTagFromPostParams struct {
+ PostID pgtype.UUID `json:"post_id"`
+ TagID pgtype.UUID `json:"tag_id"`
+}
+
+func (q *Queries) RemoveTagFromPost(ctx context.Context, arg RemoveTagFromPostParams) error {
+ _, err := q.db.Exec(ctx, removeTagFromPost, arg.PostID, arg.TagID)
+ return err
+}
diff --git a/backend/internal/db/queries/auth.sql b/backend/internal/db/queries/auth.sql
new file mode 100644
index 0000000..397c1d5
--- /dev/null
+++ b/backend/internal/db/queries/auth.sql
@@ -0,0 +1,23 @@
+-- name: CreateAuthor :one
+INSERT INTO authors (username, password_hash)
+VALUES ($1, $2) RETURNING *;
+
+-- name: GetAuthorByUsername :one
+SELECT * FROM authors
+WHERE username = $1 LIMIT 1;
+
+-- name: CreateRefreshToken :one
+INSERT INTO refresh_tokens (author_id, token_hash, expires_at)
+VALUES ($1, $2, $3) RETURNING *;
+
+-- name: GetRefreshToken :one
+SELECT * FROM refresh_tokens
+WHERE token_hash = $1 LIMIT 1;
+
+-- name: DeleteRefreshToken :exec
+DELETE FROM refresh_tokens
+WHERE token_hash = $1;
+
+-- name: DeleteRefreshTokenByAuthor :exec
+DELETE FROM refresh_tokens
+WHERE author_id = $1;
\ No newline at end of file
diff --git a/backend/internal/db/queries/posts.sql b/backend/internal/db/queries/posts.sql
new file mode 100644
index 0000000..b2fc523
--- /dev/null
+++ b/backend/internal/db/queries/posts.sql
@@ -0,0 +1,37 @@
+-- name: CreatePost :one
+INSERT INTO posts (title, slug, content, status)
+VALUES ($1, $2, $3, $4) RETURNING *;
+
+-- name: GetPostBySlug :one
+SELECT * FROM posts
+WHERE slug = $1 LIMIT 1;
+
+-- name: GetPublishedPosts :many
+SELECT * FROM posts
+WHERE status = 'published'
+ORDER BY published_at DESC
+LIMIT $1 OFFSET $2;
+
+-- name: GetPostsAdmin :many
+SELECT * FROM posts
+ORDER BY created_at DESC
+LIMIT $1 OFFSET $2;
+
+-- name: UpdatePostByID :one
+UPDATE posts SET
+ title = $2,
+ slug = $3,
+ content = $4,
+ excerpt = $5,
+ status = $6,
+ published_at = $7
+WHERE id = $1 RETURNING *;
+
+-- name: DeletePostByID :exec
+DELETE FROM posts
+WHERE id = $1;
+
+-- name: GetPostTags :many
+SELECT * FROM tags
+JOIN post_tags ON post_tags.tag_id = tags.id
+WHERE post_tags.post_id = $1;
\ No newline at end of file
diff --git a/backend/internal/db/queries/tags.sql b/backend/internal/db/queries/tags.sql
new file mode 100644
index 0000000..70ac72a
--- /dev/null
+++ b/backend/internal/db/queries/tags.sql
@@ -0,0 +1,26 @@
+-- name: CreateTag :one
+INSERT INTO tags (name, slug)
+VALUES ($1, $2) RETURNING *;
+
+-- name: GetAllTags :many
+SELECT * FROM tags;
+
+-- name: DeleteTagByID :exec
+DELETE FROM tags
+WHERE id = $1;
+
+-- name: GetPostByTagSlug :many
+SELECT * FROM posts
+JOIN post_tags ON post_tags.post_id = posts.id
+JOIN tags ON tags.id = post_tags.tag_id
+WHERE tags.slug = $1
+ORDER BY posts.published_at DESC
+LIMIT $2 OFFSET $3;
+
+-- name: AddTagToPost :exec
+INSERT INTO post_tags (post_id, tag_id)
+VALUES ($1, $2);
+
+-- name: RemoveTagFromPost :exec
+DELETE FROM post_tags
+WHERE post_id = $1 AND tag_id = $2;
\ No newline at end of file
diff --git a/backend/migrations/000001_init_schema.down.sql b/backend/migrations/000001_init_schema.down.sql
new file mode 100644
index 0000000..a5d8d8a
--- /dev/null
+++ b/backend/migrations/000001_init_schema.down.sql
@@ -0,0 +1,7 @@
+DROP TRIGGER posts_updated_at ON posts;
+DROP FUNCTION set_updated_at();
+DROP TABLE post_tags;
+DROP TABLE refresh_tokens;
+DROP TABLE posts;
+DROP TABLE tags;
+DROP TABLE authors;
\ No newline at end of file
diff --git a/backend/migrations/000001_init_schema.up.sql b/backend/migrations/000001_init_schema.up.sql
new file mode 100644
index 0000000..a2af48f
--- /dev/null
+++ b/backend/migrations/000001_init_schema.up.sql
@@ -0,0 +1,50 @@
+CREATE TABLE authors (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ username TEXT NOT NULL UNIQUE,
+ password_hash TEXT NOT NULL,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
+);
+
+CREATE TABLE tags (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ name TEXT NOT NULL UNIQUE,
+ slug TEXT NOT NULL UNIQUE
+);
+
+CREATE TABLE posts (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ title TEXT NOT NULL,
+ slug TEXT NOT NULL UNIQUE,
+ content TEXT NOT NULL,
+ excerpt TEXT,
+ status TEXT NOT NULL DEFAULT 'draft' CHECK (status in ('draft', 'published')),
+ published_at TIMESTAMPTZ,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
+);
+
+CREATE TABLE post_tags (
+ post_id UUID NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
+ tag_id UUID NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
+ PRIMARY KEY (post_id, tag_id)
+);
+
+CREATE TABLE refresh_tokens (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ author_id UUID NOT NULL REFERENCES authors(id) ON DELETE CASCADE,
+ token_hash TEXT NOT NULL,
+ expires_at TIMESTAMPTZ NOT NULL,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
+);
+
+CREATE OR REPLACE FUNCTION set_updated_at()
+RETURNS TRIGGER AS $$
+BEGIN
+ NEW.updated_at = now();
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER posts_updated_at
+ BEFORE UPDATE ON posts
+ FOR EACH ROW EXECUTE FUNCTION set_updated_at();
\ No newline at end of file
diff --git a/backend/server b/backend/server
new file mode 100755
index 0000000..b18dd4d
Binary files /dev/null and b/backend/server differ
diff --git a/backend/sqlc.yaml b/backend/sqlc.yaml
new file mode 100644
index 0000000..6b4dfe0
--- /dev/null
+++ b/backend/sqlc.yaml
@@ -0,0 +1,13 @@
+version: "2"
+sql:
+ - engine: "postgresql"
+ queries: "internal/db/queries"
+ schema: "migrations"
+ gen:
+ go:
+ package: "db"
+ out: "internal/db/generated"
+ sql_package: "pgx/v5"
+ emit_json_tags: true
+ emit_pointers_for_null_types: true
+ emit_interface: true
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..ddf9044
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,19 @@
+services:
+ db:
+ image: postgres:16-alpine
+ environment:
+ POSTGRES_DB: blog
+ POSTGRES_USER: blog
+ POSTGRES_PASSWORD: blog
+ ports:
+ - "5432:5432"
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U blog"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+
+volumes:
+ postgres_data:
\ No newline at end of file
diff --git a/root/.gitignore b/frontend/.gitignore
similarity index 100%
rename from root/.gitignore
rename to frontend/.gitignore
diff --git a/root/.vscode/extensions.json b/frontend/.vscode/extensions.json
similarity index 100%
rename from root/.vscode/extensions.json
rename to frontend/.vscode/extensions.json
diff --git a/root/.vscode/launch.json b/frontend/.vscode/launch.json
similarity index 100%
rename from root/.vscode/launch.json
rename to frontend/.vscode/launch.json
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
new file mode 100644
index 0000000..5352066
--- /dev/null
+++ b/frontend/Dockerfile
@@ -0,0 +1,21 @@
+FROM node:22-alpine AS build
+
+WORKDIR /app
+
+COPY package.json package-lock.json ./
+RUN npm ci
+
+COPY . .
+RUN npm run build
+
+FROM node:22-alpine
+
+WORKDIR /app
+
+COPY --from=build /app/dist ./dist
+COPY --from=build /app/node_modules ./node_modules
+COPY --from=build /app/package.json .
+
+EXPOSE 4321
+
+CMD ["node", "./dist/server/entry.mjs"]
diff --git a/root/README.md b/frontend/README.md
similarity index 100%
rename from root/README.md
rename to frontend/README.md
diff --git a/frontend/astro.config.mjs b/frontend/astro.config.mjs
new file mode 100644
index 0000000..980e7e7
--- /dev/null
+++ b/frontend/astro.config.mjs
@@ -0,0 +1,19 @@
+// @ts-check
+import { defineConfig } from 'astro/config';
+import node from '@astrojs/node';
+import tailwindcss from '@tailwindcss/vite';
+
+// https://astro.build/config
+export default defineConfig({
+ output: 'server',
+ adapter: node({
+ mode: 'standalone'
+ }),
+ server: {
+ port: 4321,
+ host: true
+ },
+ vite: {
+ plugins: [tailwindcss()]
+ }
+});
diff --git a/root/package-lock.json b/frontend/package-lock.json
similarity index 67%
rename from root/package-lock.json
rename to frontend/package-lock.json
index e7ac682..b2344ea 100644
--- a/root/package-lock.json
+++ b/frontend/package-lock.json
@@ -1,84 +1,50 @@
{
- "name": "root",
+ "name": "frontend",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "root",
+ "name": "frontend",
"version": "0.0.1",
"dependencies": {
- "@astrojs/alpinejs": "^0.4.9",
- "@astrojs/rss": "^4.0.15",
- "@astrojs/sitemap": "^3.7.0",
- "@expressive-code/plugin-line-numbers": "^0.41.6",
- "@fontsource/inter": "^5.2.8",
- "@iconify-json/lucide": "^1.2.89",
- "@tailwindcss/vite": "^4.1.18",
- "@types/alpinejs": "^3.13.11",
- "alpinejs": "^3.15.8",
- "astro": "^5.17.1",
- "astro-expressive-code": "^0.41.6",
- "astro-icon": "^1.1.5",
- "tailwindcss": "^4.1.18"
- }
- },
- "node_modules/@antfu/install-pkg": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
- "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==",
- "license": "MIT",
- "dependencies": {
- "package-manager-detector": "^1.3.0",
- "tinyexec": "^1.0.1"
+ "@astrojs/node": "^10.0.0",
+ "@tailwindcss/typography": "^0.5.19",
+ "@tailwindcss/vite": "^4.2.1",
+ "astro": "^6.0.0",
+ "marked": "^17.0.4",
+ "tailwindcss": "^4.2.1"
},
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
- "node_modules/@antfu/utils": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz",
- "integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
- "node_modules/@astrojs/alpinejs": {
- "version": "0.4.9",
- "resolved": "https://registry.npmjs.org/@astrojs/alpinejs/-/alpinejs-0.4.9.tgz",
- "integrity": "sha512-fvKBAugn7yIngEKfdk6vL3ZlcVKtQvFXCZznG28OikGanKN5W+PkRPIdKaW/0gThRU2FyCemgzyHgyFjsH8dTA==",
- "license": "MIT",
- "peerDependencies": {
- "@types/alpinejs": "^3.0.0",
- "alpinejs": "^3.0.0"
+ "engines": {
+ "node": ">=22.12.0"
}
},
"node_modules/@astrojs/compiler": {
- "version": "2.13.0",
- "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.0.tgz",
- "integrity": "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-3.0.0.tgz",
+ "integrity": "sha512-MwAbDE5mawZ1SS+D8qWiHdprdME5Tlj2e0YjxnEICvcOpbSukNS7Sa7hA5PK+6RrmUr/t6Gi5YgrdZKjbO/WPQ==",
"license": "MIT"
},
"node_modules/@astrojs/internal-helpers": {
- "version": "0.7.5",
- "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.5.tgz",
- "integrity": "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==",
- "license": "MIT"
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.8.0.tgz",
+ "integrity": "sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w==",
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^4.0.3"
+ }
},
"node_modules/@astrojs/markdown-remark": {
- "version": "6.3.10",
- "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.10.tgz",
- "integrity": "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.0.0.tgz",
+ "integrity": "sha512-jTAXHPy45L7o1ljH4jYV+ShtOHtyQUa1mGp3a5fJp1soX8lInuTJQ6ihmldHzVM4Q7QptU4SzIDIcKbBJO7sXQ==",
"license": "MIT",
"dependencies": {
- "@astrojs/internal-helpers": "0.7.5",
- "@astrojs/prism": "3.3.0",
+ "@astrojs/internal-helpers": "0.8.0",
+ "@astrojs/prism": "4.0.0",
"github-slugger": "^2.0.0",
"hast-util-from-html": "^2.0.3",
"hast-util-to-text": "^4.0.2",
- "import-meta-resolve": "^4.2.0",
"js-yaml": "^4.1.1",
"mdast-util-definitions": "^6.0.0",
"rehype-raw": "^7.0.0",
@@ -87,46 +53,39 @@
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"remark-smartypants": "^3.0.2",
- "shiki": "^3.19.0",
- "smol-toml": "^1.5.2",
+ "shiki": "^4.0.0",
+ "smol-toml": "^1.6.0",
"unified": "^11.0.5",
"unist-util-remove-position": "^5.0.0",
- "unist-util-visit": "^5.0.0",
+ "unist-util-visit": "^5.1.0",
"unist-util-visit-parents": "^6.0.2",
"vfile": "^6.0.3"
}
},
- "node_modules/@astrojs/prism": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz",
- "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==",
+ "node_modules/@astrojs/node": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-10.0.0.tgz",
+ "integrity": "sha512-MYz73s+U1CxdSLoYlbB9lrgA2ryi6K8ULH2rM3SBQDFbWtXuTFiBAfG8c5BHy75tsSRn2p0rc7jdFiQAzuZOyw==",
"license": "MIT",
"dependencies": {
- "prismjs": "^1.30.0"
+ "@astrojs/internal-helpers": "0.8.0",
+ "send": "^1.2.1",
+ "server-destroy": "^1.0.1"
},
- "engines": {
- "node": "18.20.8 || ^20.3.0 || >=22.0.0"
- }
- },
- "node_modules/@astrojs/rss": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-4.0.15.tgz",
- "integrity": "sha512-uXO/k6AhRkIDXmRoc6xQpoPZrimQNUmS43X4+60yunfuMNHtSRN5e/FiSi7NApcZqmugSMc5+cJi8ovqgO+qIg==",
- "license": "MIT",
- "dependencies": {
- "fast-xml-parser": "^5.3.3",
- "piccolore": "^0.1.3"
+ "peerDependencies": {
+ "astro": "^6.0.0-alpha.0"
}
},
- "node_modules/@astrojs/sitemap": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.0.tgz",
- "integrity": "sha512-+qxjUrz6Jcgh+D5VE1gKUJTA3pSthuPHe6Ao5JCxok794Lewx8hBFaWHtOnN0ntb2lfOf7gvOi9TefUswQ/ZVA==",
+ "node_modules/@astrojs/prism": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.0.tgz",
+ "integrity": "sha512-NndtNPpxaGinRpRytljGBvYHpTOwHycSZ/c+lQi5cHvkqqrHKWdkPEhImlODBNmbuB+vyQUNUDXyjzt66CihJg==",
"license": "MIT",
"dependencies": {
- "sitemap": "^8.0.2",
- "stream-replace-string": "^2.0.0",
- "zod": "^3.25.76"
+ "prismjs": "^1.30.0"
+ },
+ "engines": {
+ "node": "^20.19.1 || >=22.12.0"
}
},
"node_modules/@astrojs/telemetry": {
@@ -205,13 +164,23 @@
"node": ">=18"
}
},
- "node_modules/@ctrl/tinycolor": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz",
- "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==",
+ "node_modules/@clack/core": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.1.0.tgz",
+ "integrity": "sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA==",
"license": "MIT",
- "engines": {
- "node": ">=14"
+ "dependencies": {
+ "sisteransi": "^1.0.5"
+ }
+ },
+ "node_modules/@clack/prompts": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.1.0.tgz",
+ "integrity": "sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@clack/core": "1.1.0",
+ "sisteransi": "^1.0.5"
}
},
"node_modules/@emnapi/runtime": {
@@ -225,9 +194,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
- "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
"cpu": [
"ppc64"
],
@@ -241,9 +210,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
- "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
"cpu": [
"arm"
],
@@ -257,9 +226,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
- "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
"cpu": [
"arm64"
],
@@ -273,9 +242,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
- "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
"cpu": [
"x64"
],
@@ -289,9 +258,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
- "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
"cpu": [
"arm64"
],
@@ -305,9 +274,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
- "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
"cpu": [
"x64"
],
@@ -321,9 +290,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
- "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
"cpu": [
"arm64"
],
@@ -337,9 +306,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
- "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
"cpu": [
"x64"
],
@@ -353,9 +322,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
- "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
"cpu": [
"arm"
],
@@ -369,9 +338,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
- "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
"cpu": [
"arm64"
],
@@ -385,9 +354,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
- "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
"cpu": [
"ia32"
],
@@ -401,9 +370,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
- "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
"cpu": [
"loong64"
],
@@ -417,9 +386,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
- "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
"cpu": [
"mips64el"
],
@@ -433,9 +402,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
- "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
"cpu": [
"ppc64"
],
@@ -449,9 +418,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
- "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
"cpu": [
"riscv64"
],
@@ -465,9 +434,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
- "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
"cpu": [
"s390x"
],
@@ -481,9 +450,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
- "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
"cpu": [
"x64"
],
@@ -497,9 +466,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
- "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
"cpu": [
"arm64"
],
@@ -513,9 +482,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
- "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
"cpu": [
"x64"
],
@@ -529,9 +498,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
- "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
"cpu": [
"arm64"
],
@@ -545,9 +514,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
- "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
"cpu": [
"x64"
],
@@ -561,9 +530,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
- "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
"cpu": [
"arm64"
],
@@ -577,9 +546,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
- "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
"cpu": [
"x64"
],
@@ -593,9 +562,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
- "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
"cpu": [
"arm64"
],
@@ -609,9 +578,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
- "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
"cpu": [
"ia32"
],
@@ -625,9 +594,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
- "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
"cpu": [
"x64"
],
@@ -640,174 +609,10 @@
"node": ">=18"
}
},
- "node_modules/@expressive-code/core": {
- "version": "0.41.6",
- "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.41.6.tgz",
- "integrity": "sha512-FvJQP+hG0jWi/FLBSmvHInDqWR7jNANp9PUDjdMqSshHb0y7sxx3vHuoOr6SgXjWw+MGLqorZyPQ0aAlHEok6g==",
- "license": "MIT",
- "dependencies": {
- "@ctrl/tinycolor": "^4.0.4",
- "hast-util-select": "^6.0.2",
- "hast-util-to-html": "^9.0.1",
- "hast-util-to-text": "^4.0.1",
- "hastscript": "^9.0.0",
- "postcss": "^8.4.38",
- "postcss-nested": "^6.0.1",
- "unist-util-visit": "^5.0.0",
- "unist-util-visit-parents": "^6.0.1"
- }
- },
- "node_modules/@expressive-code/plugin-frames": {
- "version": "0.41.6",
- "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.41.6.tgz",
- "integrity": "sha512-d+hkSYXIQot6fmYnOmWAM+7TNWRv/dhfjMsNq+mIZz8Tb4mPHOcgcfZeEM5dV9TDL0ioQNvtcqQNuzA1sRPjxg==",
- "license": "MIT",
- "dependencies": {
- "@expressive-code/core": "^0.41.6"
- }
- },
- "node_modules/@expressive-code/plugin-line-numbers": {
- "version": "0.41.6",
- "resolved": "https://registry.npmjs.org/@expressive-code/plugin-line-numbers/-/plugin-line-numbers-0.41.6.tgz",
- "integrity": "sha512-YS8oLrGNBjY8qVVl6ZntwPXIh5HGrLEq23R6eyJ0tCJQmq03tCOOiWw9cc2R3J/XobXAI7coAtVbqAiGFB8pXQ==",
- "license": "MIT",
- "dependencies": {
- "@expressive-code/core": "^0.41.6"
- }
- },
- "node_modules/@expressive-code/plugin-shiki": {
- "version": "0.41.6",
- "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.41.6.tgz",
- "integrity": "sha512-Y6zmKBmsIUtWTzdefqlzm/h9Zz0Rc4gNdt2GTIH7fhHH2I9+lDYCa27BDwuBhjqcos6uK81Aca9dLUC4wzN+ng==",
- "license": "MIT",
- "dependencies": {
- "@expressive-code/core": "^0.41.6",
- "shiki": "^3.2.2"
- }
- },
- "node_modules/@expressive-code/plugin-text-markers": {
- "version": "0.41.6",
- "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.41.6.tgz",
- "integrity": "sha512-PBFa1wGyYzRExMDzBmAWC6/kdfG1oLn4pLpBeTfIRrALPjcGA/59HP3e7q9J0Smk4pC7U+lWkA2LHR8FYV8U7Q==",
- "license": "MIT",
- "dependencies": {
- "@expressive-code/core": "^0.41.6"
- }
- },
- "node_modules/@fontsource/inter": {
- "version": "5.2.8",
- "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.8.tgz",
- "integrity": "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==",
- "license": "OFL-1.1",
- "funding": {
- "url": "https://github.com/sponsors/ayuhito"
- }
- },
- "node_modules/@iconify-json/lucide": {
- "version": "1.2.89",
- "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.89.tgz",
- "integrity": "sha512-9rZaJZn8VBls1KZnGaFTnqqZrUkd++XB3vy9WYIMgmHHgLxQMEZXg3V+oJSEeit0kCNr/OfDBmrDwuGl/LZulA==",
- "license": "ISC",
- "dependencies": {
- "@iconify/types": "*"
- }
- },
- "node_modules/@iconify/tools": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@iconify/tools/-/tools-4.2.0.tgz",
- "integrity": "sha512-WRxPva/ipxYkqZd1+CkEAQmd86dQmrwH0vwK89gmp2Kh2WyyVw57XbPng0NehP3x4V1LzLsXUneP1uMfTMZmUA==",
- "license": "MIT",
- "dependencies": {
- "@iconify/types": "^2.0.0",
- "@iconify/utils": "^2.3.0",
- "cheerio": "^1.1.2",
- "domhandler": "^5.0.3",
- "extract-zip": "^2.0.1",
- "local-pkg": "^1.1.2",
- "pathe": "^2.0.3",
- "svgo": "^3.3.2",
- "tar": "^7.5.2"
- }
- },
- "node_modules/@iconify/tools/node_modules/commander": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
- "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
- "license": "MIT",
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@iconify/tools/node_modules/css-tree": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
- "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
- "license": "MIT",
- "dependencies": {
- "mdn-data": "2.0.30",
- "source-map-js": "^1.0.1"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
- }
- },
- "node_modules/@iconify/tools/node_modules/mdn-data": {
- "version": "2.0.30",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
- "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
- "license": "CC0-1.0"
- },
- "node_modules/@iconify/tools/node_modules/svgo": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
- "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
- "license": "MIT",
- "dependencies": {
- "@trysound/sax": "0.2.0",
- "commander": "^7.2.0",
- "css-select": "^5.1.0",
- "css-tree": "^2.3.1",
- "css-what": "^6.1.0",
- "csso": "^5.0.5",
- "picocolors": "^1.0.0"
- },
- "bin": {
- "svgo": "bin/svgo"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/svgo"
- }
- },
- "node_modules/@iconify/types": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
- "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
- "license": "MIT"
- },
- "node_modules/@iconify/utils": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz",
- "integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==",
- "license": "MIT",
- "dependencies": {
- "@antfu/install-pkg": "^1.0.0",
- "@antfu/utils": "^8.1.0",
- "@iconify/types": "^2.0.0",
- "debug": "^4.4.0",
- "globals": "^15.14.0",
- "kolorist": "^1.8.0",
- "local-pkg": "^1.0.0",
- "mlly": "^1.7.4"
- }
- },
"node_modules/@img/colour": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
- "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
"license": "MIT",
"optional": true,
"engines": {
@@ -1270,18 +1075,6 @@
"url": "https://opencollective.com/libvips"
}
},
- "node_modules/@isaacs/fs-minipass": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
- "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
- "license": "ISC",
- "dependencies": {
- "minipass": "^7.0.4"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -1355,16 +1148,10 @@
}
}
},
- "node_modules/@rollup/pluginutils/node_modules/estree-walker": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
- "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
- "license": "MIT"
- },
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
- "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
"cpu": [
"arm"
],
@@ -1375,9 +1162,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
- "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
"cpu": [
"arm64"
],
@@ -1388,9 +1175,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
- "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
"cpu": [
"arm64"
],
@@ -1401,9 +1188,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
- "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
"cpu": [
"x64"
],
@@ -1414,9 +1201,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
- "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
"cpu": [
"arm64"
],
@@ -1427,9 +1214,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
- "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
"cpu": [
"x64"
],
@@ -1440,9 +1227,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
- "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
"cpu": [
"arm"
],
@@ -1453,9 +1240,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
- "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
"cpu": [
"arm"
],
@@ -1466,9 +1253,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
- "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
"cpu": [
"arm64"
],
@@ -1479,9 +1266,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
- "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
"cpu": [
"arm64"
],
@@ -1492,9 +1279,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
- "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
"cpu": [
"loong64"
],
@@ -1505,9 +1292,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
- "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
"cpu": [
"loong64"
],
@@ -1518,9 +1305,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
- "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
"cpu": [
"ppc64"
],
@@ -1531,9 +1318,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
- "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
"cpu": [
"ppc64"
],
@@ -1544,9 +1331,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
- "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
"cpu": [
"riscv64"
],
@@ -1557,9 +1344,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
- "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
"cpu": [
"riscv64"
],
@@ -1570,9 +1357,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
- "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
"cpu": [
"s390x"
],
@@ -1583,9 +1370,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
- "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
"cpu": [
"x64"
],
@@ -1596,9 +1383,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
- "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
"cpu": [
"x64"
],
@@ -1609,9 +1396,9 @@
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
- "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
"cpu": [
"x64"
],
@@ -1622,9 +1409,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
- "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
"cpu": [
"arm64"
],
@@ -1635,9 +1422,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
- "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
"cpu": [
"arm64"
],
@@ -1648,9 +1435,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
- "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
"cpu": [
"ia32"
],
@@ -1661,9 +1448,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
- "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
"cpu": [
"x64"
],
@@ -1674,9 +1461,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
- "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
"cpu": [
"x64"
],
@@ -1687,64 +1474,97 @@
]
},
"node_modules/@shikijs/core": {
- "version": "3.22.0",
- "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz",
- "integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz",
+ "integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.22.0",
+ "@shikijs/primitive": "4.0.2",
+ "@shikijs/types": "4.0.2",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4",
"hast-util-to-html": "^9.0.5"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/engine-javascript": {
- "version": "3.22.0",
- "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz",
- "integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz",
+ "integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.22.0",
+ "@shikijs/types": "4.0.2",
"@shikijs/vscode-textmate": "^10.0.2",
"oniguruma-to-es": "^4.3.4"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/engine-oniguruma": {
- "version": "3.22.0",
- "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz",
- "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz",
+ "integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.22.0",
+ "@shikijs/types": "4.0.2",
"@shikijs/vscode-textmate": "^10.0.2"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/langs": {
- "version": "3.22.0",
- "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz",
- "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz",
+ "integrity": "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "4.0.2"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@shikijs/primitive": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.2.tgz",
+ "integrity": "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.22.0"
+ "@shikijs/types": "4.0.2",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/themes": {
- "version": "3.22.0",
- "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz",
- "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.2.tgz",
+ "integrity": "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.22.0"
+ "@shikijs/types": "4.0.2"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/types": {
- "version": "3.22.0",
- "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz",
- "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz",
+ "integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==",
"license": "MIT",
"dependencies": {
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/vscode-textmate": {
@@ -1754,47 +1574,47 @@
"license": "MIT"
},
"node_modules/@tailwindcss/node": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
- "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz",
+ "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==",
"license": "MIT",
"dependencies": {
- "@jridgewell/remapping": "^2.3.4",
- "enhanced-resolve": "^5.18.3",
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.19.0",
"jiti": "^2.6.1",
- "lightningcss": "1.30.2",
+ "lightningcss": "1.31.1",
"magic-string": "^0.30.21",
"source-map-js": "^1.2.1",
- "tailwindcss": "4.1.18"
+ "tailwindcss": "4.2.1"
}
},
"node_modules/@tailwindcss/oxide": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
- "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz",
+ "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==",
"license": "MIT",
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
},
"optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.1.18",
- "@tailwindcss/oxide-darwin-arm64": "4.1.18",
- "@tailwindcss/oxide-darwin-x64": "4.1.18",
- "@tailwindcss/oxide-freebsd-x64": "4.1.18",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
- "@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
- "@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
- "@tailwindcss/oxide-linux-x64-musl": "4.1.18",
- "@tailwindcss/oxide-wasm32-wasi": "4.1.18",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
- "@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
+ "@tailwindcss/oxide-android-arm64": "4.2.1",
+ "@tailwindcss/oxide-darwin-arm64": "4.2.1",
+ "@tailwindcss/oxide-darwin-x64": "4.2.1",
+ "@tailwindcss/oxide-freebsd-x64": "4.2.1",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.2.1",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.2.1",
+ "@tailwindcss/oxide-linux-x64-musl": "4.2.1",
+ "@tailwindcss/oxide-wasm32-wasi": "4.2.1",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.2.1"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
- "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz",
+ "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==",
"cpu": [
"arm64"
],
@@ -1804,13 +1624,13 @@
"android"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
- "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz",
+ "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==",
"cpu": [
"arm64"
],
@@ -1820,13 +1640,13 @@
"darwin"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
- "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz",
+ "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==",
"cpu": [
"x64"
],
@@ -1836,13 +1656,13 @@
"darwin"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
- "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz",
+ "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==",
"cpu": [
"x64"
],
@@ -1852,13 +1672,13 @@
"freebsd"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
- "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz",
+ "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==",
"cpu": [
"arm"
],
@@ -1868,13 +1688,13 @@
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
- "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz",
+ "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==",
"cpu": [
"arm64"
],
@@ -1884,13 +1704,13 @@
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
- "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz",
+ "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==",
"cpu": [
"arm64"
],
@@ -1900,13 +1720,13 @@
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
- "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz",
+ "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==",
"cpu": [
"x64"
],
@@ -1916,13 +1736,13 @@
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
- "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz",
+ "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==",
"cpu": [
"x64"
],
@@ -1932,13 +1752,13 @@
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
- "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz",
+ "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
@@ -1953,21 +1773,21 @@
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/core": "^1.7.1",
- "@emnapi/runtime": "^1.7.1",
+ "@emnapi/core": "^1.8.1",
+ "@emnapi/runtime": "^1.8.1",
"@emnapi/wasi-threads": "^1.1.0",
- "@napi-rs/wasm-runtime": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.1",
"@tybys/wasm-util": "^0.10.1",
- "tslib": "^2.4.0"
+ "tslib": "^2.8.1"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
- "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz",
+ "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==",
"cpu": [
"arm64"
],
@@ -1977,13 +1797,13 @@
"win32"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz",
- "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz",
+ "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==",
"cpu": [
"x64"
],
@@ -1993,38 +1813,35 @@
"win32"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
- "node_modules/@tailwindcss/vite": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz",
- "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==",
+ "node_modules/@tailwindcss/typography": {
+ "version": "0.5.19",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
+ "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
"license": "MIT",
"dependencies": {
- "@tailwindcss/node": "4.1.18",
- "@tailwindcss/oxide": "4.1.18",
- "tailwindcss": "4.1.18"
+ "postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
- "vite": "^5.2.0 || ^6 || ^7"
+ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
}
},
- "node_modules/@trysound/sax": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
- "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
- "license": "ISC",
- "engines": {
- "node": ">=10.13.0"
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz",
+ "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.2.1",
+ "@tailwindcss/oxide": "4.2.1",
+ "tailwindcss": "4.2.1"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7"
}
},
- "node_modules/@types/alpinejs": {
- "version": "3.13.11",
- "resolved": "https://registry.npmjs.org/@types/alpinejs/-/alpinejs-3.13.11.tgz",
- "integrity": "sha512-3KhGkDixCPiLdL3Z/ok1GxHwLxEWqQOKJccgaQL01wc0EVM2tCTaqlC3NIedmxAXkVzt/V6VTM8qPgnOHKJ1MA==",
- "license": "MIT"
- },
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@@ -2073,200 +1890,56 @@
"@types/unist": "*"
}
},
- "node_modules/@types/node": {
- "version": "25.2.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz",
- "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==",
- "license": "MIT",
- "dependencies": {
- "undici-types": "~7.16.0"
- }
- },
- "node_modules/@types/sax": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
- "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
"license": "MIT"
},
- "node_modules/@types/yauzl": {
- "version": "2.10.3",
- "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
- "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@ungap/structured-clone": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"license": "ISC"
},
- "node_modules/@vue/reactivity": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
- "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
- "license": "MIT",
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "license": "ISC",
"dependencies": {
- "@vue/shared": "3.1.5"
- }
- },
- "node_modules/@vue/shared": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
- "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
- "license": "MIT"
- },
- "node_modules/acorn": {
- "version": "8.15.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
},
"engines": {
- "node": ">=0.4.0"
+ "node": ">= 8"
}
},
- "node_modules/alpinejs": {
- "version": "3.15.8",
- "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.8.tgz",
- "integrity": "sha512-zxIfCRTBGvF1CCLIOMQOxAyBuqibxSEwS6Jm1a3HGA9rgrJVcjEWlwLcQTVGAWGS8YhAsTRLVrtQ5a5QT9bSSQ==",
+ "node_modules/anymatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"license": "MIT",
- "dependencies": {
- "@vue/reactivity": "~3.1.1"
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/ansi-align": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
- "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.1.0"
- }
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
},
- "node_modules/ansi-align/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "license": "MIT",
+ "node_modules/aria-query": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+ "license": "Apache-2.0",
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-align/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "license": "MIT"
- },
- "node_modules/ansi-align/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-align/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-regex": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
- "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/anymatch/node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/arg": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
- "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
- "license": "MIT"
- },
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "license": "Python-2.0"
- },
- "node_modules/aria-query": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
- "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">= 0.4"
+ "node": ">= 0.4"
}
},
"node_modules/array-iterate": {
@@ -2280,80 +1953,72 @@
}
},
"node_modules/astro": {
- "version": "5.17.1",
- "resolved": "https://registry.npmjs.org/astro/-/astro-5.17.1.tgz",
- "integrity": "sha512-oD3tlxTaVWGq/Wfbqk6gxzVRz98xa/rYlpe+gU2jXJMSD01k6sEDL01ZlT8mVSYB/rMgnvIOfiQQ3BbLdN237A==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/astro/-/astro-6.0.0.tgz",
+ "integrity": "sha512-i24mxDGXUzyfKfE4IcWIiLBv9ENDVz98u/TLhjkknGkt/2KIjkR4Q0HeabtaVmIhyWECGd2E61ZeBdFuJ902xw==",
"license": "MIT",
"dependencies": {
- "@astrojs/compiler": "^2.13.0",
- "@astrojs/internal-helpers": "0.7.5",
- "@astrojs/markdown-remark": "6.3.10",
+ "@astrojs/compiler": "^3.0.0",
+ "@astrojs/internal-helpers": "0.8.0",
+ "@astrojs/markdown-remark": "7.0.0",
"@astrojs/telemetry": "3.3.0",
"@capsizecss/unpack": "^4.0.0",
+ "@clack/prompts": "^1.0.1",
"@oslojs/encoding": "^1.1.0",
"@rollup/pluginutils": "^5.3.0",
- "acorn": "^8.15.0",
"aria-query": "^5.3.2",
"axobject-query": "^4.1.0",
- "boxen": "8.0.1",
- "ci-info": "^4.3.1",
+ "ci-info": "^4.4.0",
"clsx": "^2.1.1",
- "common-ancestor-path": "^1.0.1",
+ "common-ancestor-path": "^2.0.0",
"cookie": "^1.1.1",
- "cssesc": "^3.0.0",
- "debug": "^4.4.3",
- "deterministic-object-hash": "^2.0.2",
- "devalue": "^5.6.2",
+ "devalue": "^5.6.3",
"diff": "^8.0.3",
"dlv": "^1.1.3",
"dset": "^3.1.4",
- "es-module-lexer": "^1.7.0",
- "esbuild": "^0.25.0",
- "estree-walker": "^3.0.3",
+ "es-module-lexer": "^2.0.0",
+ "esbuild": "^0.27.3",
"flattie": "^1.1.1",
- "fontace": "~0.4.0",
+ "fontace": "~0.4.1",
"github-slugger": "^2.0.0",
"html-escaper": "3.0.3",
"http-cache-semantics": "^4.2.0",
- "import-meta-resolve": "^4.2.0",
"js-yaml": "^4.1.1",
"magic-string": "^0.30.21",
- "magicast": "^0.5.1",
+ "magicast": "^0.5.2",
"mrmime": "^2.0.1",
"neotraverse": "^0.6.18",
- "p-limit": "^6.2.0",
- "p-queue": "^8.1.1",
+ "obug": "^2.1.1",
+ "p-limit": "^7.3.0",
+ "p-queue": "^9.1.0",
"package-manager-detector": "^1.6.0",
"piccolore": "^0.1.3",
"picomatch": "^4.0.3",
- "prompts": "^2.4.2",
"rehype": "^13.0.2",
- "semver": "^7.7.3",
- "shiki": "^3.21.0",
+ "semver": "^7.7.4",
+ "shiki": "^4.0.0",
"smol-toml": "^1.6.0",
"svgo": "^4.0.0",
+ "tinyclip": "^0.1.6",
"tinyexec": "^1.0.2",
"tinyglobby": "^0.2.15",
"tsconfck": "^3.1.6",
"ultrahtml": "^1.6.0",
- "unifont": "~0.7.3",
- "unist-util-visit": "^5.0.0",
+ "unifont": "~0.7.4",
+ "unist-util-visit": "^5.1.0",
"unstorage": "^1.17.4",
"vfile": "^6.0.3",
- "vite": "^6.4.1",
- "vitefu": "^1.1.1",
+ "vite": "^7.3.1",
+ "vitefu": "^1.1.2",
"xxhash-wasm": "^1.1.0",
- "yargs-parser": "^21.1.1",
- "yocto-spinner": "^0.2.3",
- "zod": "^3.25.76",
- "zod-to-json-schema": "^3.25.1",
- "zod-to-ts": "^1.2.0"
+ "yargs-parser": "^22.0.0",
+ "zod": "^4.3.6"
},
"bin": {
- "astro": "astro.js"
+ "astro": "bin/astro.mjs"
},
"engines": {
- "node": "18.20.8 || ^20.3.0 || >=22.0.0",
+ "node": "^20.19.1 || >=22.12.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0"
},
@@ -2365,29 +2030,6 @@
"sharp": "^0.34.0"
}
},
- "node_modules/astro-expressive-code": {
- "version": "0.41.6",
- "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.41.6.tgz",
- "integrity": "sha512-l47tb1uhmVIebHUkw+HEPtU/av0G4O8Q34g2cbkPvC7/e9ZhANcjUUciKt9Hp6gSVDdIuXBBLwJQn2LkeGMOAw==",
- "license": "MIT",
- "dependencies": {
- "rehype-expressive-code": "^0.41.6"
- },
- "peerDependencies": {
- "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta"
- }
- },
- "node_modules/astro-icon": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/astro-icon/-/astro-icon-1.1.5.tgz",
- "integrity": "sha512-CJYS5nWOw9jz4RpGWmzNQY7D0y2ZZacH7atL2K9DeJXJVaz7/5WrxeyIxO8KASk1jCM96Q4LjRx/F3R+InjJrw==",
- "license": "MIT",
- "dependencies": {
- "@iconify/tools": "^4.0.5",
- "@iconify/types": "^2.0.0",
- "@iconify/utils": "^2.1.30"
- }
- },
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -2407,71 +2049,12 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/base-64": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
- "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==",
- "license": "MIT"
- },
- "node_modules/bcp-47-match": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz",
- "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC"
},
- "node_modules/boxen": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz",
- "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==",
- "license": "MIT",
- "dependencies": {
- "ansi-align": "^3.0.1",
- "camelcase": "^8.0.0",
- "chalk": "^5.3.0",
- "cli-boxes": "^3.0.0",
- "string-width": "^7.2.0",
- "type-fest": "^4.21.0",
- "widest-line": "^5.0.0",
- "wrap-ansi": "^9.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/buffer-crc32": {
- "version": "0.2.13",
- "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
- "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
- "license": "MIT",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/camelcase": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
- "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==",
- "license": "MIT",
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/ccount": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
@@ -2482,18 +2065,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/chalk": {
- "version": "5.6.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
- "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
- "license": "MIT",
- "engines": {
- "node": "^12.17.0 || ^14.13 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
"node_modules/character-entities": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
@@ -2524,48 +2095,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/cheerio": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
- "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
- "license": "MIT",
- "dependencies": {
- "cheerio-select": "^2.1.0",
- "dom-serializer": "^2.0.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.2.2",
- "encoding-sniffer": "^0.2.1",
- "htmlparser2": "^10.1.0",
- "parse5": "^7.3.0",
- "parse5-htmlparser2-tree-adapter": "^7.1.0",
- "parse5-parser-stream": "^7.1.2",
- "undici": "^7.19.0",
- "whatwg-mimetype": "^4.0.0"
- },
- "engines": {
- "node": ">=20.18.1"
- },
- "funding": {
- "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
- }
- },
- "node_modules/cheerio-select": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
- "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
- "license": "BSD-2-Clause",
- "dependencies": {
- "boolbase": "^1.0.0",
- "css-select": "^5.1.0",
- "css-what": "^6.1.0",
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/fb55"
- }
- },
"node_modules/chokidar": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
@@ -2581,15 +2110,6 @@
"url": "https://paulmillr.com/funding/"
}
},
- "node_modules/chownr": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
- "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/ci-info": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
@@ -2605,18 +2125,6 @@
"node": ">=8"
}
},
- "node_modules/cli-boxes": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
- "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -2646,16 +2154,13 @@
}
},
"node_modules/common-ancestor-path": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz",
- "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==",
- "license": "ISC"
- },
- "node_modules/confbox": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
- "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
- "license": "MIT"
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz",
+ "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">= 18"
+ }
},
"node_modules/cookie": {
"version": "1.1.1",
@@ -2701,30 +2206,14 @@
"url": "https://github.com/sponsors/fb55"
}
},
- "node_modules/css-selector-parser": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz",
- "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/mdevils"
- },
- {
- "type": "patreon",
- "url": "https://patreon.com/mdevils"
- }
- ],
- "license": "MIT"
- },
"node_modules/css-tree": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
- "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
+ "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
"license": "MIT",
"dependencies": {
- "mdn-data": "2.12.2",
- "source-map-js": "^1.0.1"
+ "mdn-data": "2.27.1",
+ "source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
@@ -2823,6 +2312,15 @@
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
"license": "MIT"
},
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -2847,22 +2345,10 @@
"node": ">=8"
}
},
- "node_modules/deterministic-object-hash": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz",
- "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==",
- "license": "MIT",
- "dependencies": {
- "base-64": "^1.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/devalue": {
- "version": "5.6.2",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz",
- "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==",
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz",
+ "integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==",
"license": "MIT"
},
"node_modules/devlop": {
@@ -2887,19 +2373,6 @@
"node": ">=0.3.1"
}
},
- "node_modules/direction": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz",
- "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==",
- "license": "MIT",
- "bin": {
- "direction": "cli.js"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -2982,38 +2455,25 @@
"node": ">=4"
}
},
- "node_modules/emoji-regex": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
- "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
- "node_modules/encoding-sniffer": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
- "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
- "license": "MIT",
- "dependencies": {
- "iconv-lite": "^0.6.3",
- "whatwg-encoding": "^3.1.1"
- },
- "funding": {
- "url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
- }
- },
- "node_modules/end-of-stream": {
- "version": "1.4.5",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
- "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
- "dependencies": {
- "once": "^1.4.0"
+ "engines": {
+ "node": ">= 0.8"
}
},
"node_modules/enhanced-resolve": {
- "version": "5.19.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
- "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
+ "version": "5.20.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
+ "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -3036,15 +2496,15 @@
}
},
"node_modules/es-module-lexer": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
- "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
+ "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
"license": "MIT"
},
"node_modules/esbuild": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
- "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -3054,33 +2514,39 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.12",
- "@esbuild/android-arm": "0.25.12",
- "@esbuild/android-arm64": "0.25.12",
- "@esbuild/android-x64": "0.25.12",
- "@esbuild/darwin-arm64": "0.25.12",
- "@esbuild/darwin-x64": "0.25.12",
- "@esbuild/freebsd-arm64": "0.25.12",
- "@esbuild/freebsd-x64": "0.25.12",
- "@esbuild/linux-arm": "0.25.12",
- "@esbuild/linux-arm64": "0.25.12",
- "@esbuild/linux-ia32": "0.25.12",
- "@esbuild/linux-loong64": "0.25.12",
- "@esbuild/linux-mips64el": "0.25.12",
- "@esbuild/linux-ppc64": "0.25.12",
- "@esbuild/linux-riscv64": "0.25.12",
- "@esbuild/linux-s390x": "0.25.12",
- "@esbuild/linux-x64": "0.25.12",
- "@esbuild/netbsd-arm64": "0.25.12",
- "@esbuild/netbsd-x64": "0.25.12",
- "@esbuild/openbsd-arm64": "0.25.12",
- "@esbuild/openbsd-x64": "0.25.12",
- "@esbuild/openharmony-arm64": "0.25.12",
- "@esbuild/sunos-x64": "0.25.12",
- "@esbuild/win32-arm64": "0.25.12",
- "@esbuild/win32-ia32": "0.25.12",
- "@esbuild/win32-x64": "0.25.12"
- }
+ "@esbuild/aix-ppc64": "0.27.3",
+ "@esbuild/android-arm": "0.27.3",
+ "@esbuild/android-arm64": "0.27.3",
+ "@esbuild/android-x64": "0.27.3",
+ "@esbuild/darwin-arm64": "0.27.3",
+ "@esbuild/darwin-x64": "0.27.3",
+ "@esbuild/freebsd-arm64": "0.27.3",
+ "@esbuild/freebsd-x64": "0.27.3",
+ "@esbuild/linux-arm": "0.27.3",
+ "@esbuild/linux-arm64": "0.27.3",
+ "@esbuild/linux-ia32": "0.27.3",
+ "@esbuild/linux-loong64": "0.27.3",
+ "@esbuild/linux-mips64el": "0.27.3",
+ "@esbuild/linux-ppc64": "0.27.3",
+ "@esbuild/linux-riscv64": "0.27.3",
+ "@esbuild/linux-s390x": "0.27.3",
+ "@esbuild/linux-x64": "0.27.3",
+ "@esbuild/netbsd-arm64": "0.27.3",
+ "@esbuild/netbsd-x64": "0.27.3",
+ "@esbuild/openbsd-arm64": "0.27.3",
+ "@esbuild/openbsd-x64": "0.27.3",
+ "@esbuild/openharmony-arm64": "0.27.3",
+ "@esbuild/sunos-x64": "0.27.3",
+ "@esbuild/win32-arm64": "0.27.3",
+ "@esbuild/win32-ia32": "0.27.3",
+ "@esbuild/win32-x64": "0.27.3"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
},
"node_modules/escape-string-regexp": {
"version": "5.0.0",
@@ -3095,12 +2561,18 @@
}
},
"node_modules/estree-walker": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
- "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
- "dependencies": {
- "@types/estree": "^1.0.0"
+ "engines": {
+ "node": ">= 0.6"
}
},
"node_modules/eventemitter3": {
@@ -3109,77 +2581,12 @@
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
"license": "MIT"
},
- "node_modules/expressive-code": {
- "version": "0.41.6",
- "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.41.6.tgz",
- "integrity": "sha512-W/5+IQbrpCIM5KGLjO35wlp1NCwDOOVQb+PAvzEoGkW1xjGM807ZGfBKptNWH6UECvt6qgmLyWolCMYKh7eQmA==",
- "license": "MIT",
- "dependencies": {
- "@expressive-code/core": "^0.41.6",
- "@expressive-code/plugin-frames": "^0.41.6",
- "@expressive-code/plugin-shiki": "^0.41.6",
- "@expressive-code/plugin-text-markers": "^0.41.6"
- }
- },
- "node_modules/exsolve": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
- "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
- "license": "MIT"
- },
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
- "node_modules/extract-zip": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
- "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
- "license": "BSD-2-Clause",
- "dependencies": {
- "debug": "^4.1.1",
- "get-stream": "^5.1.0",
- "yauzl": "^2.10.0"
- },
- "bin": {
- "extract-zip": "cli.js"
- },
- "engines": {
- "node": ">= 10.17.0"
- },
- "optionalDependencies": {
- "@types/yauzl": "^2.9.1"
- }
- },
- "node_modules/fast-xml-parser": {
- "version": "5.3.4",
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz",
- "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/NaturalIntelligence"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "strnum": "^2.1.0"
- },
- "bin": {
- "fxparser": "src/cli/cli.js"
- }
- },
- "node_modules/fd-slicer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
- "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
- "license": "MIT",
- "dependencies": {
- "pend": "~1.2.0"
- }
- },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -3216,9 +2623,9 @@
}
},
"node_modules/fontkitten": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.2.tgz",
- "integrity": "sha512-piJxbLnkD9Xcyi7dWJRnqszEURixe7CrF/efBfbffe2DPyabmuIuqraruY8cXTs19QoM8VJzx47BDRVNXETM7Q==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz",
+ "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==",
"license": "MIT",
"dependencies": {
"tiny-inflate": "^1.0.3"
@@ -3227,6 +2634,15 @@
"node": ">=20"
}
},
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3241,51 +2657,12 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/get-east-asian-width": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
- "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/get-stream": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
- "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
- "license": "MIT",
- "dependencies": {
- "pump": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/github-slugger": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
"license": "ISC"
},
- "node_modules/globals": {
- "version": "15.15.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
- "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -3293,9 +2670,9 @@
"license": "ISC"
},
"node_modules/h3": {
- "version": "1.15.5",
- "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz",
- "integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==",
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.6.tgz",
+ "integrity": "sha512-oi15ESLW5LRthZ+qPCi5GNasY/gvynSKUQxgiovrY63bPAtG59wtM+LSrlcwvOHAXzGrXVLnI97brbkdPF9WoQ==",
"license": "MIT",
"dependencies": {
"cookie-es": "^1.2.2",
@@ -3347,19 +2724,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/hast-util-has-property": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz",
- "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
"node_modules/hast-util-is-element": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
@@ -3411,33 +2775,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/hast-util-select": {
- "version": "6.0.4",
- "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz",
- "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "@types/unist": "^3.0.0",
- "bcp-47-match": "^2.0.0",
- "comma-separated-tokens": "^2.0.0",
- "css-selector-parser": "^3.0.0",
- "devlop": "^1.0.0",
- "direction": "^2.0.0",
- "hast-util-has-property": "^3.0.0",
- "hast-util-to-string": "^3.0.0",
- "hast-util-whitespace": "^3.0.0",
- "nth-check": "^2.0.0",
- "property-information": "^7.0.0",
- "space-separated-tokens": "^2.0.0",
- "unist-util-visit": "^5.0.0",
- "zwitch": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
"node_modules/hast-util-to-html": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
@@ -3480,19 +2817,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/hast-util-to-string": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz",
- "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
"node_modules/hast-util-to-text": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
@@ -3555,65 +2879,38 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/htmlparser2": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
- "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
- "funding": [
- "https://github.com/fb55/htmlparser2?sponsor=1",
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.2.2",
- "entities": "^7.0.1"
- }
- },
- "node_modules/htmlparser2/node_modules/entities": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
- "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
"node_modules/http-cache-semantics": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
"integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
"license": "BSD-2-Clause"
},
- "node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
},
"engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/import-meta-resolve": {
- "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==",
- "license": "MIT",
+ "node": ">= 0.8"
+ },
"funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
"node_modules/iron-webcrypto": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
@@ -3638,15 +2935,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-inside-container": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
@@ -3678,9 +2966,9 @@
}
},
"node_modules/is-wsl": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
- "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
+ "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
"license": "MIT",
"dependencies": {
"is-inside-container": "^1.0.0"
@@ -3713,25 +3001,10 @@
"js-yaml": "bin/js-yaml.js"
}
},
- "node_modules/kleur": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
- "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/kolorist": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
- "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
- "license": "MIT"
- },
"node_modules/lightningcss": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
- "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
+ "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==",
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
@@ -3744,23 +3017,23 @@
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
- "lightningcss-android-arm64": "1.30.2",
- "lightningcss-darwin-arm64": "1.30.2",
- "lightningcss-darwin-x64": "1.30.2",
- "lightningcss-freebsd-x64": "1.30.2",
- "lightningcss-linux-arm-gnueabihf": "1.30.2",
- "lightningcss-linux-arm64-gnu": "1.30.2",
- "lightningcss-linux-arm64-musl": "1.30.2",
- "lightningcss-linux-x64-gnu": "1.30.2",
- "lightningcss-linux-x64-musl": "1.30.2",
- "lightningcss-win32-arm64-msvc": "1.30.2",
- "lightningcss-win32-x64-msvc": "1.30.2"
+ "lightningcss-android-arm64": "1.31.1",
+ "lightningcss-darwin-arm64": "1.31.1",
+ "lightningcss-darwin-x64": "1.31.1",
+ "lightningcss-freebsd-x64": "1.31.1",
+ "lightningcss-linux-arm-gnueabihf": "1.31.1",
+ "lightningcss-linux-arm64-gnu": "1.31.1",
+ "lightningcss-linux-arm64-musl": "1.31.1",
+ "lightningcss-linux-x64-gnu": "1.31.1",
+ "lightningcss-linux-x64-musl": "1.31.1",
+ "lightningcss-win32-arm64-msvc": "1.31.1",
+ "lightningcss-win32-x64-msvc": "1.31.1"
}
},
"node_modules/lightningcss-android-arm64": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
- "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz",
+ "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==",
"cpu": [
"arm64"
],
@@ -3778,9 +3051,9 @@
}
},
"node_modules/lightningcss-darwin-arm64": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
- "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz",
+ "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==",
"cpu": [
"arm64"
],
@@ -3798,9 +3071,9 @@
}
},
"node_modules/lightningcss-darwin-x64": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
- "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz",
+ "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==",
"cpu": [
"x64"
],
@@ -3818,9 +3091,9 @@
}
},
"node_modules/lightningcss-freebsd-x64": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
- "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz",
+ "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==",
"cpu": [
"x64"
],
@@ -3838,9 +3111,9 @@
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
- "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz",
+ "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==",
"cpu": [
"arm"
],
@@ -3858,9 +3131,9 @@
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
- "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz",
+ "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==",
"cpu": [
"arm64"
],
@@ -3878,9 +3151,9 @@
}
},
"node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
- "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz",
+ "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==",
"cpu": [
"arm64"
],
@@ -3898,9 +3171,9 @@
}
},
"node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
- "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz",
+ "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==",
"cpu": [
"x64"
],
@@ -3918,9 +3191,9 @@
}
},
"node_modules/lightningcss-linux-x64-musl": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
- "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz",
+ "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==",
"cpu": [
"x64"
],
@@ -3938,9 +3211,9 @@
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
- "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz",
+ "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==",
"cpu": [
"arm64"
],
@@ -3958,9 +3231,9 @@
}
},
"node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
- "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz",
+ "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==",
"cpu": [
"x64"
],
@@ -3977,23 +3250,6 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/local-pkg": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
- "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==",
- "license": "MIT",
- "dependencies": {
- "mlly": "^1.7.4",
- "pkg-types": "^2.3.0",
- "quansync": "^0.2.11"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
"node_modules/longest-streak": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
@@ -4005,9 +3261,9 @@
}
},
"node_modules/lru-cache": {
- "version": "11.2.5",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz",
- "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==",
+ "version": "11.2.6",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
+ "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
@@ -4023,13 +3279,13 @@
}
},
"node_modules/magicast": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz",
- "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==",
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz",
+ "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.5",
- "@babel/types": "^7.28.5",
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
"source-map-js": "^1.2.1"
}
},
@@ -4043,6 +3299,18 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/marked": {
+ "version": "17.0.4",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.4.tgz",
+ "integrity": "sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==",
+ "license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 20"
+ }
+ },
"node_modules/mdast-util-definitions": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz",
@@ -4075,9 +3343,9 @@
}
},
"node_modules/mdast-util-from-markdown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
- "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
+ "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
@@ -4269,9 +3537,9 @@
}
},
"node_modules/mdn-data": {
- "version": "2.12.2",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
- "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "version": "2.27.1",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
+ "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
"license": "CC0-1.0"
},
"node_modules/micromark": {
@@ -4837,54 +4105,29 @@
],
"license": "MIT"
},
- "node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "license": "ISC",
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
"engines": {
- "node": ">=16 || 14 >=14.17"
+ "node": ">= 0.6"
}
},
- "node_modules/minizlib": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
- "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"license": "MIT",
"dependencies": {
- "minipass": "^7.1.2"
+ "mime-db": "^1.54.0"
},
"engines": {
- "node": ">= 18"
- }
- },
- "node_modules/mlly": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
- "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
- "license": "MIT",
- "dependencies": {
- "acorn": "^8.15.0",
- "pathe": "^2.0.3",
- "pkg-types": "^1.3.1",
- "ufo": "^1.6.1"
- }
- },
- "node_modules/mlly/node_modules/confbox": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
- "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
- "license": "MIT"
- },
- "node_modules/mlly/node_modules/pkg-types": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
- "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
- "license": "MIT",
- "dependencies": {
- "confbox": "^0.1.8",
- "mlly": "^1.7.4",
- "pathe": "^2.0.1"
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/mrmime": {
@@ -4975,6 +4218,16 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
"node_modules/ofetch": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz",
@@ -4992,13 +4245,16 @@
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
"license": "MIT"
},
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "license": "ISC",
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
"dependencies": {
- "wrappy": "1"
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
}
},
"node_modules/oniguruma-parser": {
@@ -5019,43 +4275,43 @@
}
},
"node_modules/p-limit": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz",
- "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==",
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz",
+ "integrity": "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==",
"license": "MIT",
"dependencies": {
- "yocto-queue": "^1.1.1"
+ "yocto-queue": "^1.2.1"
},
"engines": {
- "node": ">=18"
+ "node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-queue": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz",
- "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==",
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz",
+ "integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==",
"license": "MIT",
"dependencies": {
"eventemitter3": "^5.0.1",
- "p-timeout": "^6.1.2"
+ "p-timeout": "^7.0.0"
},
"engines": {
- "node": ">=18"
+ "node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-timeout": {
- "version": "6.1.4",
- "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz",
- "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz",
+ "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==",
"license": "MIT",
"engines": {
- "node": ">=14.16"
+ "node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -5097,43 +4353,6 @@
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
- "node_modules/parse5-htmlparser2-tree-adapter": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
- "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
- "license": "MIT",
- "dependencies": {
- "domhandler": "^5.0.3",
- "parse5": "^7.0.0"
- },
- "funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
- }
- },
- "node_modules/parse5-parser-stream": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
- "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
- "license": "MIT",
- "dependencies": {
- "parse5": "^7.0.0"
- },
- "funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
- }
- },
- "node_modules/pathe": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
- "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
- "license": "MIT"
- },
- "node_modules/pend": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
- "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
- "license": "MIT"
- },
"node_modules/piccolore": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz",
@@ -5158,21 +4377,10 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/pkg-types": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
- "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
- "license": "MIT",
- "dependencies": {
- "confbox": "^0.2.2",
- "exsolve": "^1.0.7",
- "pathe": "^2.0.3"
- }
- },
"node_modules/postcss": {
- "version": "8.5.6",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
- "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
"funding": [
{
"type": "opencollective",
@@ -5197,35 +4405,10 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/postcss-nested": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
- "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "postcss-selector-parser": "^6.1.1"
- },
- "engines": {
- "node": ">=12.0"
- },
- "peerDependencies": {
- "postcss": "^8.2.14"
- }
- },
"node_modules/postcss-selector-parser": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
- "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
@@ -5244,19 +4427,6 @@
"node": ">=6"
}
},
- "node_modules/prompts": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
- "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
- "license": "MIT",
- "dependencies": {
- "kleur": "^3.0.3",
- "sisteransi": "^1.0.5"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/property-information": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
@@ -5267,38 +4437,21 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/pump": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
- "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
- "license": "MIT",
- "dependencies": {
- "end-of-stream": "^1.1.0",
- "once": "^1.3.1"
- }
- },
- "node_modules/quansync": {
- "version": "0.2.11",
- "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
- "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/antfu"
- },
- {
- "type": "individual",
- "url": "https://github.com/sponsors/sxzz"
- }
- ],
- "license": "MIT"
- },
"node_modules/radix3": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz",
"integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==",
"license": "MIT"
},
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/readdirp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
@@ -5352,15 +4505,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/rehype-expressive-code": {
- "version": "0.41.6",
- "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.41.6.tgz",
- "integrity": "sha512-aBMX8kxPtjmDSFUdZlAWJkMvsQ4ZMASfee90JWIAV8tweltXLzkWC3q++43ToTelI8ac5iC0B3/S/Cl4Ql1y2g==",
- "license": "MIT",
- "dependencies": {
- "expressive-code": "^0.41.6"
- }
- },
"node_modules/rehype-parse": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz",
@@ -5549,9 +4693,9 @@
}
},
"node_modules/rollup": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
- "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@@ -5564,53 +4708,47 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.57.1",
- "@rollup/rollup-android-arm64": "4.57.1",
- "@rollup/rollup-darwin-arm64": "4.57.1",
- "@rollup/rollup-darwin-x64": "4.57.1",
- "@rollup/rollup-freebsd-arm64": "4.57.1",
- "@rollup/rollup-freebsd-x64": "4.57.1",
- "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
- "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
- "@rollup/rollup-linux-arm64-gnu": "4.57.1",
- "@rollup/rollup-linux-arm64-musl": "4.57.1",
- "@rollup/rollup-linux-loong64-gnu": "4.57.1",
- "@rollup/rollup-linux-loong64-musl": "4.57.1",
- "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
- "@rollup/rollup-linux-ppc64-musl": "4.57.1",
- "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
- "@rollup/rollup-linux-riscv64-musl": "4.57.1",
- "@rollup/rollup-linux-s390x-gnu": "4.57.1",
- "@rollup/rollup-linux-x64-gnu": "4.57.1",
- "@rollup/rollup-linux-x64-musl": "4.57.1",
- "@rollup/rollup-openbsd-x64": "4.57.1",
- "@rollup/rollup-openharmony-arm64": "4.57.1",
- "@rollup/rollup-win32-arm64-msvc": "4.57.1",
- "@rollup/rollup-win32-ia32-msvc": "4.57.1",
- "@rollup/rollup-win32-x64-gnu": "4.57.1",
- "@rollup/rollup-win32-x64-msvc": "4.57.1",
+ "@rollup/rollup-android-arm-eabi": "4.59.0",
+ "@rollup/rollup-android-arm64": "4.59.0",
+ "@rollup/rollup-darwin-arm64": "4.59.0",
+ "@rollup/rollup-darwin-x64": "4.59.0",
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
+ "@rollup/rollup-freebsd-x64": "4.59.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
+ "@rollup/rollup-openbsd-x64": "4.59.0",
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
"fsevents": "~2.3.2"
}
},
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "license": "MIT"
- },
"node_modules/sax": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
- "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz",
+ "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
}
},
"node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -5619,6 +4757,44 @@
"node": ">=10"
}
},
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/server-destroy": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz",
+ "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==",
+ "license": "ISC"
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
"node_modules/sharp": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
@@ -5665,19 +4841,22 @@
}
},
"node_modules/shiki": {
- "version": "3.22.0",
- "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz",
- "integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.2.tgz",
+ "integrity": "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==",
"license": "MIT",
"dependencies": {
- "@shikijs/core": "3.22.0",
- "@shikijs/engine-javascript": "3.22.0",
- "@shikijs/engine-oniguruma": "3.22.0",
- "@shikijs/langs": "3.22.0",
- "@shikijs/themes": "3.22.0",
- "@shikijs/types": "3.22.0",
+ "@shikijs/core": "4.0.2",
+ "@shikijs/engine-javascript": "4.0.2",
+ "@shikijs/engine-oniguruma": "4.0.2",
+ "@shikijs/langs": "4.0.2",
+ "@shikijs/themes": "4.0.2",
+ "@shikijs/types": "4.0.2",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/sisteransi": {
@@ -5686,31 +4865,6 @@
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"license": "MIT"
},
- "node_modules/sitemap": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.2.tgz",
- "integrity": "sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "^17.0.5",
- "@types/sax": "^1.2.1",
- "arg": "^5.0.0",
- "sax": "^1.4.1"
- },
- "bin": {
- "sitemap": "dist/cli.js"
- },
- "engines": {
- "node": ">=14.0.0",
- "npm": ">=6.0.0"
- }
- },
- "node_modules/sitemap/node_modules/@types/node": {
- "version": "17.0.45",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz",
- "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==",
- "license": "MIT"
- },
"node_modules/smol-toml": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz",
@@ -5742,27 +4896,13 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/stream-replace-string": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz",
- "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==",
- "license": "MIT"
- },
- "node_modules/string-width": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
- "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
- "dependencies": {
- "emoji-regex": "^10.3.0",
- "get-east-asian-width": "^1.0.0",
- "strip-ansi": "^7.1.0"
- },
"engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">= 0.8"
}
},
"node_modules/stringify-entities": {
@@ -5779,37 +4919,10 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/strip-ansi": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
- "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/strnum": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz",
- "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/NaturalIntelligence"
- }
- ],
- "license": "MIT"
- },
"node_modules/svgo": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz",
- "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz",
+ "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==",
"license": "MIT",
"dependencies": {
"commander": "^11.1.0",
@@ -5818,7 +4931,7 @@
"css-what": "^6.1.0",
"csso": "^5.0.5",
"picocolors": "^1.1.1",
- "sax": "^1.4.1"
+ "sax": "^1.5.0"
},
"bin": {
"svgo": "bin/svgo.js"
@@ -5832,9 +4945,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
- "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz",
+ "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==",
"license": "MIT"
},
"node_modules/tapable": {
@@ -5850,28 +4963,21 @@
"url": "https://opencollective.com/webpack"
}
},
- "node_modules/tar": {
- "version": "7.5.7",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
- "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/fs-minipass": "^4.0.0",
- "chownr": "^3.0.0",
- "minipass": "^7.1.2",
- "minizlib": "^3.1.0",
- "yallist": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
"license": "MIT"
},
+ "node_modules/tinyclip": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.12.tgz",
+ "integrity": "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^16.14.0 || >= 17.3.0"
+ }
+ },
"node_modules/tinyexec": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
@@ -5897,6 +5003,15 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
"node_modules/trim-lines": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
@@ -5944,32 +5059,6 @@
"license": "0BSD",
"optional": true
},
- "node_modules/type-fest": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
- "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "license": "Apache-2.0",
- "peer": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
"node_modules/ufo": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
@@ -5988,21 +5077,6 @@
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==",
"license": "MIT"
},
- "node_modules/undici": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.21.0.tgz",
- "integrity": "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==",
- "license": "MIT",
- "engines": {
- "node": ">=20.18.1"
- }
- },
- "node_modules/undici-types": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
- "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
- "license": "MIT"
- },
"node_modules/unified": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
@@ -6023,9 +5097,9 @@
}
},
"node_modules/unifont": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.3.tgz",
- "integrity": "sha512-b0GtQzKCyuSHGsfj5vyN8st7muZ6VCI4XD4vFlr7Uy1rlWVYxC3npnfk8MyreHxJYrz1ooLDqDzFe9XqQTlAhA==",
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.4.tgz",
+ "integrity": "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==",
"license": "MIT",
"dependencies": {
"css-tree": "^3.1.0",
@@ -6301,23 +5375,23 @@
}
},
"node_modules/vite": {
- "version": "6.4.1",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
- "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"license": "MIT",
"dependencies": {
- "esbuild": "^0.25.0",
- "fdir": "^6.4.4",
- "picomatch": "^4.0.2",
- "postcss": "^8.5.3",
- "rollup": "^4.34.9",
- "tinyglobby": "^0.2.13"
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ "node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -6326,14 +5400,14 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
- "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@types/node": "^20.19.0 || >=22.12.0",
"jiti": ">=1.21.0",
- "less": "*",
+ "less": "^4.0.0",
"lightningcss": "^1.21.0",
- "sass": "*",
- "sass-embedded": "*",
- "stylus": "*",
- "sugarss": "*",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
@@ -6375,9 +5449,9 @@
}
},
"node_modules/vitefu": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz",
- "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz",
+ "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==",
"license": "MIT",
"workspaces": [
"tests/deps/*",
@@ -6385,7 +5459,7 @@
"tests/projects/workspace/packages/*"
],
"peerDependencies": {
- "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0"
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0"
},
"peerDependenciesMeta": {
"vite": {
@@ -6403,28 +5477,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/whatwg-encoding": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
- "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
- "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
- "license": "MIT",
- "dependencies": {
- "iconv-lite": "0.6.3"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/whatwg-mimetype": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
- "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/which-pm-runs": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz",
@@ -6434,76 +5486,19 @@
"node": ">=4"
}
},
- "node_modules/widest-line": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz",
- "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==",
- "license": "MIT",
- "dependencies": {
- "string-width": "^7.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/wrap-ansi": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
- "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.2.1",
- "string-width": "^7.0.0",
- "strip-ansi": "^7.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "license": "ISC"
- },
"node_modules/xxhash-wasm": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz",
"integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==",
"license": "MIT"
},
- "node_modules/yallist": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
- "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "version": "22.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
+ "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
"license": "ISC",
"engines": {
- "node": ">=12"
- }
- },
- "node_modules/yauzl": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
- "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
- "license": "MIT",
- "dependencies": {
- "buffer-crc32": "~0.2.3",
- "fd-slicer": "~1.1.0"
+ "node": "^20.19.0 || ^22.12.0 || >=23"
}
},
"node_modules/yocto-queue": {
@@ -6518,60 +5513,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/yocto-spinner": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz",
- "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==",
- "license": "MIT",
- "dependencies": {
- "yoctocolors": "^2.1.1"
- },
- "engines": {
- "node": ">=18.19"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/yoctocolors": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz",
- "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/zod": {
- "version": "3.25.76",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
- "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
- "node_modules/zod-to-json-schema": {
- "version": "3.25.1",
- "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz",
- "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==",
- "license": "ISC",
- "peerDependencies": {
- "zod": "^3.25 || ^4"
- }
- },
- "node_modules/zod-to-ts": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz",
- "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==",
- "peerDependencies": {
- "typescript": "^4.9.4 || ^5.0.2",
- "zod": "^3"
- }
- },
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..0e74e22
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "frontend",
+ "type": "module",
+ "version": "0.0.1",
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "scripts": {
+ "dev": "astro dev",
+ "build": "astro build",
+ "preview": "astro preview",
+ "astro": "astro"
+ },
+ "dependencies": {
+ "@astrojs/node": "^10.0.0",
+ "@tailwindcss/typography": "^0.5.19",
+ "@tailwindcss/vite": "^4.2.1",
+ "astro": "^6.0.0",
+ "marked": "^17.0.4",
+ "tailwindcss": "^4.2.1"
+ }
+}
diff --git a/root/public/favicon.ico b/frontend/public/favicon.ico
similarity index 100%
rename from root/public/favicon.ico
rename to frontend/public/favicon.ico
diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg
new file mode 100644
index 0000000..f157bd1
--- /dev/null
+++ b/frontend/public/favicon.svg
@@ -0,0 +1,9 @@
+
diff --git a/frontend/src/components/Footer.astro b/frontend/src/components/Footer.astro
new file mode 100644
index 0000000..bc10c1d
--- /dev/null
+++ b/frontend/src/components/Footer.astro
@@ -0,0 +1,9 @@
+---
+const year = new Date().getFullYear();
+---
+
+
diff --git a/frontend/src/components/Header.astro b/frontend/src/components/Header.astro
new file mode 100644
index 0000000..39ecfc7
--- /dev/null
+++ b/frontend/src/components/Header.astro
@@ -0,0 +1,12 @@
+---
+---
+
+
{excerpt}
+No posts yet.
+ ) : ( +No posts with this tag.
+ ) : ( +No tags yet.
+ ) : ( +{formatter.pubDate.toString().slice(0, 15)}
-{formatter.description}
-I'll add a picture here...
-- I see you've come here to find out more about me, so here is a - rundown. I'm a Korean born but internationally raised third - culture kid (TCK) that ended up in Kyoto, Japan for his studies. - I study CS at Kyoto University with the specialization on IoT - systems and network security. I intend to stay based in Japan - for the next half decade or so, but I'm down to move wherever I - can shine. Now let's go over some details about me, shall we? -
-- My definition of home has never been set place. Sometimes - its South Korea, another time its where my family is. At one point, - home was where I found the most comfort existing. And after living - in five very distinct countries — South Korea, Oman, Philippines, - Malta, and Japan — I've realized that I don't need to force - a definition on the word. As long as I can form some meaning in my - stay, then I can make that my home(s). -
-- Although this might sound obvious to some, it took a while for - myself to fully digest, especially so as a young adult. Moving - from country to country -
-- I've moved a bunch and went to a number of schools, but here's a - list of the most recent I've dropped by. -
-- {ed.years} -
- {ed.note &&{ed.note}
} -- Hi there! Welcome to my little corner of the internet. My name's - YJ and I'm a CS major at Kyoto University hoping to make it - someday. To keep things simple, this is my personal website - where I share my projects, thoughts, and experiences. This - ranges from my notes on solving coding problems to something - interesting I read about. Feel free to explore and connect with - me! -
-