feat(deploy): app.rubis.arthurbarre.fr — image, manifests K3s, route Traefik
Some checks failed
Build & Deploy Landing / build-and-deploy (push) Successful in 31s
Build & Deploy App / build-and-deploy (push) Failing after 46s

Premier déploiement de l'app SaaS (apps/api + apps/web) — distinct de la
landing déjà sur rubis.arthurbarre.fr. Architecture :
- Image unique (Dockerfile.app, multi-stage) : AdonisJS sert l'API ET le
  SPA static via @adonisjs/static + wildcard fallback pour TanStack Router
- Workers BullMQ tournent dans le même process Node (cf. start/queue.ts)
- Redis 7 dans le namespace rubis (PVC local-path 1Gi)
- Migrations en init-container avant le serveur (idempotent)

Infra :
- K3s namespace rubis (déjà existant) — ajout deploy/svc rubis-app + redis
- NodePort 30110 → Traefik → app.rubis.arthurbarre.fr (TLS Let's Encrypt)
- Postgres : base rubis_prod + user rubis créés sur 10.10.10.3
- MinIO : bucket rubis-prod-invoices créé via mc
- Secrets K3s posés via kubectl create secret (APP_KEY généré, DB pwd
  généré, MinIO root creds réutilisées, Resend/Mistral keys)
- DNS OVH A record app.rubis créé (id 5413305619)
- CI Gitea : .gitea/workflows/deploy-app.yml séparé du workflow landing,
  filtres sur paths apps/**, packages/**, Dockerfile.app, k3s/app/**

Code app :
- Static middleware @adonisjs/static configuré
- Wildcard route SPA fallback en fin de routes.ts
- Fix erreurs strict TS qui bloquaient le build vite (unused vars,
  Client missing contactFirstName/LastName dans MSW)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ordinarthur 2026-05-07 02:01:39 +02:00
parent ca95dde9b3
commit 461ab9bcd9
16 changed files with 565 additions and 12 deletions

View File

@ -0,0 +1,90 @@
name: Build & Deploy App
# Workflow pour l'app SaaS (apps/api AdonisJS + apps/web React) déployée
# sur app.rubis.arthurbarre.fr. Image distincte de la landing.
on:
push:
branches: [main]
paths:
- 'apps/**'
- 'packages/**'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'package.json'
- 'turbo.json'
- 'Dockerfile.app'
- 'k3s/app/**'
- '.gitea/workflows/deploy-app.yml'
env:
REGISTRY: git.arthurbarre.fr
IMAGE: ordinarthur/rubis-app
NAMESPACE: rubis
DEPLOYMENT: rubis-app
CONTAINER: app
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Gitea Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ordinarthur
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and push app image
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile.app
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE }}:cache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE }}:cache,mode=max
- name: Install kubectl
run: |
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl /usr/local/bin/
- name: Deploy to K3s
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
kubectl apply -f k3s/namespace.yml
# Idempotent : on (re)pose le pull secret du registry Gitea + le
# secret applicatif n'est PAS recréé ici (créé manuellement au
# premier deploy via kubectl, contient des creds qui ne
# transitent jamais par le CI).
kubectl -n $NAMESPACE create secret docker-registry gitea-registry \
--docker-server=$REGISTRY \
--docker-username=ordinarthur \
--docker-password=${{ secrets.REGISTRY_PASSWORD }} \
--dry-run=client -o yaml | kubectl apply -f -
# Apply Redis + app manifests (idempotent)
kubectl apply -f k3s/app/
# Pin l'image avec le sha du commit pour rolling update propre.
# Le init-container migrate utilise la même image et tourne avant
# le serveur — migrations idempotentes via ace migration:run.
kubectl -n $NAMESPACE set image deployment/$DEPLOYMENT \
$CONTAINER=$REGISTRY/$IMAGE:${{ github.sha }}
# Patch aussi le init container (même image)
kubectl -n $NAMESPACE patch deployment $DEPLOYMENT \
--type='json' \
-p="[{\"op\":\"replace\",\"path\":\"/spec/template/spec/initContainers/0/image\",\"value\":\"$REGISTRY/$IMAGE:${{ github.sha }}\"}]"
kubectl -n $NAMESPACE rollout status deployment/$DEPLOYMENT --timeout=300s

View File

@ -1,8 +1,17 @@
name: Build & Deploy
name: Build & Deploy Landing
# Workflow pour la landing static (rubis.arthurbarre.fr).
# L'app SaaS (apps/api + apps/web) a son propre workflow : deploy-app.yml.
on:
push:
branches: [main]
paths:
- 'landing/**'
- 'Dockerfile'
- 'k3s/namespace.yml'
- 'k3s/deployment.yml'
- 'k3s/service.yml'
- '.gitea/workflows/deploy.yml'
env:
REGISTRY: git.arthurbarre.fr

111
Dockerfile.app Normal file
View File

@ -0,0 +1,111 @@
# syntax=docker/dockerfile:1.7
# =============================================================================
# Rubis Sur l'Ongle — image production de l'app SaaS (apps/api + apps/web)
# Sert app.rubis.arthurbarre.fr. La landing (rubis.arthurbarre.fr) reste sur
# une image séparée — Dockerfile à la racine, nginx static.
# =============================================================================
#
# Multi-stage :
# - base : node 22 alpine + pnpm + tini
# - deps : install workspace deps (cache friendly via manifests d'abord)
# - build : build shared, web, api ; copie le SPA dans apps/api/build/public
# - runner : copie le repo "pruned" prod, lance node bin/server.js
#
# Choix architectural : un seul process Node sert l'API ET le SPA static
# (via le static middleware AdonisJS + un fallback wildcard pour SPA routing).
# Les workers BullMQ tournent dans le même process (cf. start/queue.ts).
# =============================================================================
ARG NODE_VERSION=22.13.1
ARG PNPM_VERSION=10.0.0
# -----------------------------------------------------------------------------
# base — node + pnpm + tini
# -----------------------------------------------------------------------------
FROM node:${NODE_VERSION}-alpine AS base
ARG PNPM_VERSION
RUN apk add --no-cache libc6-compat tini && \
corepack enable && \
corepack prepare pnpm@${PNPM_VERSION} --activate
WORKDIR /repo
# -----------------------------------------------------------------------------
# deps — install workspace (devDeps inclus, on en a besoin pour les builds)
# -----------------------------------------------------------------------------
FROM base AS deps
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json tsconfig.base.json ./
COPY apps/api/package.json ./apps/api/
COPY apps/web/package.json ./apps/web/
COPY packages/shared/package.json ./packages/shared/
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile
# -----------------------------------------------------------------------------
# build — shared → web → api, puis copie du SPA dans le build de l'API
# -----------------------------------------------------------------------------
FROM deps AS build
COPY packages/shared ./packages/shared
COPY apps/web ./apps/web
COPY apps/api ./apps/api
# Builds :
# - @rubis/shared : pas de build (TS source consommé directement via exports).
# - Web : on appelle vite build directement (le `tsc -b` du script de prod
# fait remonter des erreurs DOM dans @tanstack/router-core sans cache
# .tsbuildinfo ; le typecheck est fait en CI séparément).
# - API : `node ace build` (canonique AdonisJS V7) — produit apps/api/build
# avec compiled JS, package.json runtime, et metaFiles configurés.
#
# Note : ce build peut planter en cross-compile ARM→amd64 (swc/core), donc
# en local sur Mac silicon, builder pour --platform linux/arm64. Le CI
# Gitea tourne nativement sur linux/amd64 et n'a pas le problème.
RUN pnpm --filter @rubis/web exec vite build && \
pnpm --filter @rubis/api build
# Le SPA static va dans apps/api/build/public/ pour être servi par le static
# middleware AdonisJS. AdonisJS ne copie pas public/ par défaut dans build/
# (metaFiles vide), on le fait manuellement ici.
RUN mkdir -p apps/api/build/public && \
cp -r apps/web/dist/. apps/api/build/public/
# Prune les devDeps. Les symlinks pnpm vers les workspace packages
# (@rubis/shared) restent valides car on garde le repo en place.
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm install --prod --frozen-lockfile=false
# -----------------------------------------------------------------------------
# runner — runtime minimal, user non-root
# -----------------------------------------------------------------------------
FROM base AS runner
RUN addgroup -g 1001 -S nodejs && adduser -S adonis -u 1001
ENV NODE_ENV=production \
HOST=0.0.0.0 \
PORT=3333 \
LOG_LEVEL=info
WORKDIR /app
# On copie tout le repo pruned (node_modules inclus avec les symlinks
# workspace). C'est plus gros qu'une image "deploy" pure, mais ça évite
# les pièges de résolution workspace pour V1.
COPY --from=build --chown=adonis:nodejs /repo /app
USER adonis
WORKDIR /app/apps/api
EXPOSE 3333
# Healthcheck léger : le serveur HTTP doit répondre 200 sur /api/v1/.
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD wget -qO- http://127.0.0.1:3333/ >/dev/null 2>&1 || exit 1
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "build/bin/server.js"]

View File

@ -59,6 +59,7 @@ export default defineConfig({
() => import('@adonisjs/limiter/limiter_provider'),
() => import('@adonisjs/mail/mail_provider'),
() => import('@adonisjs/drive/drive_provider'),
() => import('@adonisjs/static/static_provider')
],
/*
@ -110,7 +111,10 @@ export default defineConfig({
| the production build.
|
*/
metaFiles: [],
metaFiles: [{
pattern: 'public/**',
reloadServer: false,
}],
hooks: {
init: [

17
apps/api/config/static.ts Normal file
View File

@ -0,0 +1,17 @@
import { defineConfig } from '@adonisjs/static'
/**
* Configuration options to tweak the static files middleware.
* The complete set of options are documented on the
* official documentation website.
*
* https://docs.adonisjs.com/guides/static-assets
*/
const staticServerConfig = defineConfig({
enabled: true,
etag: true,
lastModified: true,
dotFiles: 'ignore',
})
export default staticServerConfig

View File

@ -68,6 +68,7 @@
"@adonisjs/mail": "^10.2.0",
"@adonisjs/session": "^8.1.0",
"@adonisjs/shield": "^9.0.0",
"@adonisjs/static": "^2.0.1",
"@aws-sdk/client-s3": "^3.1043.0",
"@aws-sdk/s3-request-presigner": "^3.1043.0",
"@japa/api-client": "^3.2.1",

View File

@ -26,6 +26,7 @@ server.use([
() => import('#middleware/force_json_response_middleware'),
() => import('#middleware/container_bindings_middleware'),
() => import('@adonisjs/cors/cors_middleware'),
() => import('@adonisjs/static/static_middleware')
])
/**

View File

@ -12,10 +12,9 @@
import { middleware } from '#start/kernel'
import router from '@adonisjs/core/services/router'
import { controllers } from '#generated/controllers'
router.get('/', () => {
return { hello: 'world' }
})
import app from '@adonisjs/core/services/app'
import { existsSync } from 'node:fs'
import { resolve } from 'node:path'
router
.group(() => {
@ -178,3 +177,25 @@ router
.use(middleware.auth())
})
.prefix('/api/v1')
/**
* SPA fallback sert `public/index.html` pour toute route non-API et non-asset
* statique (le static middleware aura déjà répondu pour les vrais fichiers
* via etag / 200). Permet à TanStack Router côté front de gérer son routing
* sans 404 quand l'utilisateur recharge sur /factures, /plans/nouveau, etc.
*
* On ne match PAS /api/v1/* (gardé en 404 propre via le routeur ci-dessus).
*/
router.get('*', async ({ request, response }) => {
if (request.url().startsWith('/api/')) {
return response.status(404).json({
errors: [{ code: 'not_found', message: 'Route introuvable' }],
})
}
const indexPath = resolve(app.publicPath('index.html'))
if (existsSync(indexPath)) {
return response.download(indexPath)
}
// En dev sans build front, on renvoie un message clair.
return response.status(503).send('SPA not built — run `pnpm --filter @rubis/web build`')
})

View File

@ -59,18 +59,16 @@ export function CadenceCalendar({
onAddStep,
onAddStepAtOffset,
onRemoveStep,
globalTone,
}: {
steps: DraftStepLite[];
selectedIndex?: number;
onSelectStep?: (idx: number) => void;
onUpdateStep?: (idx: number, patch: Partial<DraftStepLite>) => void;
onAddStep: () => void;
/** Création directe en cliquant une case vide. Reçoit l'offset depuis l'échéance. */
/** Création directe en cliquant une case vide. Reçoit l'offset depuis
* l'échéance. La tonalité par défaut est gérée côté parent. */
onAddStepAtOffset?: (offsetDays: number) => void;
onRemoveStep: (idx: number) => void;
/** Tonalité par défaut pour une étape créée depuis le calendrier. */
globalTone?: RelanceTone;
}) {
const dueDate = useMemo(() => {
const now = new Date();

View File

@ -29,7 +29,7 @@ const generateSchema = z.object({
*/
function mockGenerate(
tone: z.infer<typeof generateSchema>["tone"],
offsetDays: number,
_offsetDays: number,
prompt: string,
): { subject: string; body: string } {
const briefSuffix = prompt.trim() ? ` (${prompt.trim().slice(0, 60)}…)` : "";

View File

@ -403,6 +403,8 @@ export const invoiceHandlers = [
const created = mockDb.createClient(orgId, {
name: fields.clientName,
email: fields.clientEmail,
contactFirstName: null,
contactLastName: null,
phone: null,
address: null,
siret: null,
@ -546,6 +548,8 @@ export const invoiceHandlers = [
const created = mockDb.createClient(orgId, {
name: fields.clientName,
email: fields.clientEmail!,
contactFirstName: null,
contactLastName: null,
phone: null,
address: null,
siret: null,
@ -569,6 +573,8 @@ export const invoiceHandlers = [
const created = mockDb.createClient(orgId, {
name: fields.clientName,
email: fields.clientEmail!,
contactFirstName: null,
contactLastName: null,
phone: null,
address: null,
siret: null,

View File

@ -475,7 +475,6 @@ function StepCadence({ draft, onChange }: { draft: Draft; onChange: (d: Draft) =
<CadenceCalendar
steps={draft.steps}
selectedIndex={selectedIdx}
globalTone={draft.globalTone}
// Toggle : re-cliquer une étape déjà sélectionnée la désélectionne
// pour revenir à la vue d'ensemble.
onSelectStep={(idx) =>

117
k3s/app/deployment.yml Normal file
View File

@ -0,0 +1,117 @@
# Rubis App — apps/api (AdonisJS) + apps/web (React) bundlés dans une image
# unique. Le SPA est servi par le static middleware AdonisJS, le wildcard
# fallback gère le routing TanStack Router. Workers BullMQ tournent dans le
# même process (cf. start/queue.ts).
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: rubis-app
namespace: rubis
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: rubis-app
template:
metadata:
labels:
app: rubis-app
spec:
imagePullSecrets:
- name: gitea-registry
# Migrations exécutées en init-container avant que le serveur démarre.
# Idempotent (ace migration:run skip ce qui est déjà appliqué).
initContainers:
- name: migrate
image: git.arthurbarre.fr/ordinarthur/rubis-app:latest
workingDir: /app/apps/api
command: ['node', 'ace', 'migration:run', '--force']
envFrom:
- secretRef: { name: rubis-app-secrets }
- configMapRef: { name: rubis-app-config }
resources:
requests: { cpu: 50m, memory: 128Mi }
limits: { cpu: 500m, memory: 512Mi }
containers:
- name: app
image: git.arthurbarre.fr/ordinarthur/rubis-app:latest
imagePullPolicy: Always
ports:
- containerPort: 3333
name: http
envFrom:
- secretRef: { name: rubis-app-secrets }
- configMapRef: { name: rubis-app-config }
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 1000m
memory: 768Mi
# Probes adaptées au boot AdonisJS (peut prendre 10-15s avec BullMQ)
startupProbe:
httpGet: { path: /, port: http }
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 30 # 150s max
livenessProbe:
httpGet: { path: /, port: http }
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet: { path: /, port: http }
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
---
apiVersion: v1
kind: ConfigMap
metadata:
name: rubis-app-config
namespace: rubis
data:
# Variables non-sensibles. Les secrets sont dans rubis-app-secrets.
TZ: 'Europe/Paris'
PORT: '3333'
HOST: '0.0.0.0'
NODE_ENV: 'production'
LOG_LEVEL: 'info'
APP_URL: 'https://app.rubis.arthurbarre.fr'
WEB_URL: 'https://app.rubis.arthurbarre.fr'
SESSION_DRIVER: 'cookie'
COOKIE_SECURE: 'true'
COOKIE_DOMAIN: 'app.rubis.arthurbarre.fr'
DB_CONNECTION: 'postgres'
PG_HOST: '10.10.10.3'
PG_PORT: '5432'
PG_USER: 'rubis'
PG_DB_NAME: 'rubis_prod'
REDIS_HOST: 'rubis-redis.rubis.svc.cluster.local'
REDIS_PORT: '6379'
LIMITER_STORE: 'redis'
# MinIO interne (pas via le NodePort public, plus rapide + plus sécurisé).
DRIVE_DISK: 's3'
S3_ENDPOINT: 'http://minio.minio.svc.cluster.local:9000'
S3_REGION: 'fr-par'
S3_BUCKET: 'rubis-prod-invoices'
S3_FORCE_PATH_STYLE: 'true'
MAIL_DRIVER: 'resend'
MAIL_FROM_ADDRESS: 'rubis@arthurbarre.fr'
MAIL_FROM_NAME: "Rubis Sur l'Ongle"
OCR_PROVIDER: 'mistral'
ACCESS_TOKEN_TTL_MINUTES: '30'
REFRESH_TOKEN_TTL_DAYS: '30'

84
k3s/app/redis.yml Normal file
View File

@ -0,0 +1,84 @@
# Redis 7 — backend de BullMQ (queues `relances`, `checkins`) et du cache.
# Single replica, suffisant V1. Persistance via local-path PVC pour ne pas
# perdre les jobs schedulés en cas de redémarrage du pod.
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: rubis-redis-data
namespace: rubis
spec:
accessModes: [ReadWriteOnce]
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: rubis-redis
namespace: rubis
spec:
replicas: 1
strategy:
type: Recreate # PVC RWO → pas de rollout simultané
selector:
matchLabels:
app: rubis-redis
template:
metadata:
labels:
app: rubis-redis
spec:
containers:
- name: redis
image: redis:7.4-alpine
args:
- redis-server
- --appendonly
- 'yes'
- --maxmemory
- 256mb
- --maxmemory-policy
- allkeys-lru
ports:
- containerPort: 6379
name: redis
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 384Mi
volumeMounts:
- name: data
mountPath: /data
livenessProbe:
tcpSocket: { port: redis }
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
exec:
command: [redis-cli, ping]
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: data
persistentVolumeClaim:
claimName: rubis-redis-data
---
apiVersion: v1
kind: Service
metadata:
name: rubis-redis
namespace: rubis
spec:
type: ClusterIP
selector:
app: rubis-redis
ports:
- port: 6379
targetPort: redis
name: redis

17
k3s/app/service.yml Normal file
View File

@ -0,0 +1,17 @@
# NodePort 30110 — exposé par Traefik sur la gateway VM (cf.
# ansible/roles/traefik/templates/rubis-app.yml.j2).
---
apiVersion: v1
kind: Service
metadata:
name: rubis-app
namespace: rubis
spec:
type: NodePort
selector:
app: rubis-app
ports:
- port: 80
targetPort: http
nodePort: 30110
name: http

78
pnpm-lock.yaml generated
View File

@ -62,6 +62,9 @@ importers:
'@adonisjs/shield':
specifier: ^9.0.0
version: 9.0.0(adb93641c3908819f9462459e2e86e5c)
'@adonisjs/static':
specifier: ^2.0.1
version: 2.0.1(@adonisjs/assembler@8.4.0(typescript@6.0.3))(@adonisjs/core@7.3.2(@adonisjs/assembler@8.4.0(typescript@6.0.3))(@vinejs/vine@4.4.0)(pino-pretty@13.1.3)(youch@4.1.1))
'@aws-sdk/client-s3':
specifier: ^3.1043.0
version: 3.1043.0
@ -638,6 +641,16 @@ packages:
edge.js:
optional: true
'@adonisjs/static@2.0.1':
resolution: {integrity: sha512-pXSyQgha1yfyQDXPp0ywv7hJqk4JgCCkzWLkku2PW3Y6i/9GUTyzzNTCpz29loZzSF0b/EpiHXQE/sqBhW+p7w==}
engines: {node: '>=24.0.0'}
peerDependencies:
'@adonisjs/assembler': ^8.0.0-next.7 || ^8.0.0
'@adonisjs/core': ^7.0.0-next.0 || ^7.0.0
peerDependenciesMeta:
'@adonisjs/assembler':
optional: true
'@adonisjs/tsconfig@2.0.0':
resolution: {integrity: sha512-Uz8qvB6KR9otCh9zei2VEj7tPwdsrT7R+voYoN4tUfEijWRdTNgJ8d1CtyLKHaggCCOwZIwZLVklV/h2FDHgNw==}
@ -3470,6 +3483,9 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
@ -3736,6 +3752,10 @@ packages:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
fresh@2.0.0:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
@ -4580,6 +4600,10 @@ packages:
parse5@7.3.0:
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
path-browserify@1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
@ -4777,6 +4801,10 @@ packages:
resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==}
engines: {node: '>= 0.8'}
range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
rate-limiter-flexible@9.1.1:
resolution: {integrity: sha512-imxFjzPCmvDLMe7d2tsgiSQvs5EI2fI9SNymmslAfOqznZhsZ+PqbIjIYKpuSbd3pKovR1aMG47qfCLIO/adVg==}
@ -4977,6 +5005,10 @@ packages:
engines: {node: '>=10'}
hasBin: true
send@1.2.1:
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
engines: {node: '>= 18'}
seroval-plugins@1.5.4:
resolution: {integrity: sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==}
engines: {node: '>=10'}
@ -4987,6 +5019,10 @@ packages:
resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==}
engines: {node: '>=10'}
serve-static@2.2.1:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'}
set-cookie-parser@2.7.2:
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
@ -6032,6 +6068,15 @@ snapshots:
'@japa/api-client': 3.2.1(@japa/assert@4.2.0(@japa/runner@5.3.0))(@japa/runner@5.3.0)
'@japa/plugin-adonisjs': 5.2.0(@adonisjs/core@7.3.2(@adonisjs/assembler@8.4.0(typescript@6.0.3))(@vinejs/vine@4.4.0)(pino-pretty@13.1.3)(youch@4.1.1))(@japa/api-client@3.2.1(@japa/assert@4.2.0(@japa/runner@5.3.0))(@japa/runner@5.3.0))(@japa/runner@5.3.0)
'@adonisjs/static@2.0.1(@adonisjs/assembler@8.4.0(typescript@6.0.3))(@adonisjs/core@7.3.2(@adonisjs/assembler@8.4.0(typescript@6.0.3))(@vinejs/vine@4.4.0)(pino-pretty@13.1.3)(youch@4.1.1))':
dependencies:
'@adonisjs/core': 7.3.2(@adonisjs/assembler@8.4.0(typescript@6.0.3))(@vinejs/vine@4.4.0)(pino-pretty@13.1.3)(youch@4.1.1)
serve-static: 2.2.1
optionalDependencies:
'@adonisjs/assembler': 8.4.0(typescript@6.0.3)
transitivePeerDependencies:
- supports-color
'@adonisjs/tsconfig@2.0.0': {}
'@antfu/install-pkg@1.1.0':
@ -9061,6 +9106,8 @@ snapshots:
escalade@3.2.0: {}
escape-html@1.0.3: {}
escape-string-regexp@1.0.5: {}
escape-string-regexp@4.0.0: {}
@ -9397,6 +9444,8 @@ snapshots:
fresh@0.5.2: {}
fresh@2.0.0: {}
fs-constants@1.0.0: {}
fsevents@2.3.3:
@ -10132,6 +10181,8 @@ snapshots:
dependencies:
entities: 6.0.1
parseurl@1.3.3: {}
path-browserify@1.0.1: {}
path-exists@4.0.0: {}
@ -10327,6 +10378,8 @@ snapshots:
random-bytes@1.0.0: {}
range-parser@1.2.1: {}
rate-limiter-flexible@9.1.1: {}
raw-body@3.0.2:
@ -10542,12 +10595,37 @@ snapshots:
semver@7.7.4: {}
send@1.2.1:
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
transitivePeerDependencies:
- supports-color
seroval-plugins@1.5.4(seroval@1.5.4):
dependencies:
seroval: 1.5.4
seroval@1.5.4: {}
serve-static@2.2.1:
dependencies:
encodeurl: 2.0.0
escape-html: 1.0.3
parseurl: 1.3.3
send: 1.2.1
transitivePeerDependencies:
- supports-color
set-cookie-parser@2.7.2: {}
set-cookie-parser@3.1.0: {}