mirror of
https://github.com/arthur-pbty/flint.git
synced 2026-08-01 20:29:03 +02:00
reorganizage du projet et mise en place d'un vrai build
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
node_modules
|
||||
dist
|
||||
build
|
||||
.git
|
||||
.gitignore
|
||||
.env
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
node_modules/
|
||||
dist/
|
||||
build/
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
@@ -8,22 +8,19 @@ Ce fichier décrit le projet, sa structure, les conventions de code et la procé
|
||||
|
||||
Vue d'ensemble du projet
|
||||
-----------------------
|
||||
- Tech stack: TypeScript + Discord.js (bot template). Le code source est dans `src/` et la sortie build dans `dist/` (à ignorer).
|
||||
- Localisation: `locales/` contient `en.json`, `fr.json`, `es.json`.
|
||||
- Scripts utiles: `scripts/deployCommands.ts` pour (re)déployer les slash commands.
|
||||
- Tech stack: TypeScript + Discord.js (bot template). Le code source est dans `src/` et la sortie build dans `build/` (à ignorer).
|
||||
- Localisation: `src/i18n/` contient `en.json`, `fr.json`, `es.json`.
|
||||
|
||||
Organisation des fichiers
|
||||
------------------------
|
||||
- Racine:
|
||||
- `package.json`, `tsconfig.json`, `Dockerfile`, `docker-compose.yml` — scripts et infra.
|
||||
- `locales/` — fichiers de traduction.
|
||||
- `src/` (code TypeScript):
|
||||
- `index.ts` — point d'entrée, boot du bot.
|
||||
- `commands/` — définitions de commandes (chaque fichier expose une commande).
|
||||
- `events/` — un fichier par événement Discord (ex: `guildMemberAdd.ts`). `src/events/index.ts` centralise l'enregistrement.
|
||||
- `framework/` — bibliothèque interne: helpers commandes, `i18n/`, `memberMessages/`, `presence/`, `execution/`, `handlers/`, `config/`, `types/`.
|
||||
- `utils/` — helpers génériques (ex: `templateVariables.ts`).
|
||||
- `scripts/` — utilitaires (ex: `deployCommands.ts`).
|
||||
- `tests/` — tests unitaires ciblant managers, stores et utilitaires.
|
||||
|
||||
Principes et conventions de code
|
||||
@@ -33,7 +30,7 @@ Principes et conventions de code
|
||||
- Events: un seul fichier par événement; exporter une fonction `registerX(client, i18n)` ou une fonction d'enregistrement équivalente. Centraliser les imports dans `src/events/index.ts`.
|
||||
- Typage: utiliser TypeScript strict, signatures fortement typées pour les handlers (`onPrefixMessage(message: Message)` etc.).
|
||||
- Tests: privilégier les tests unitaires pour managers/stores/transformations. Mockez les adaptateurs Discord.
|
||||
- i18n: utiliser `I18nService` pour traductions. Ajouter toutes les clés dans `locales/*.json`.
|
||||
- i18n: utiliser `I18nService` pour traductions. Ajouter toutes les clés dans `src/i18n/*.json`.
|
||||
- Nommage: fichiers en lowerCamelCase (ex: `welcome.ts`, `memberMessagePanel.ts`). Exports nommés préférés pour faciliter le mocking.
|
||||
- Sécurité: ne pas committer de secrets (`.env*` doit être dans `.gitignore`).
|
||||
|
||||
@@ -42,9 +39,9 @@ Procédure standard pour ajouter une nouvelle commande
|
||||
1. Créer le fichier dans `src/commands/myCommand.ts`.
|
||||
2. Utiliser `defineCommand({ meta, args, examples, execute })` pour déclarer la commande.
|
||||
3. Implémenter `execute()` de façon minimale: valider les args et appeler un service dans `src/framework/` si la logique est non triviale.
|
||||
4. Ajouter les clés de traduction dans `locales/en.json`, `locales/fr.json`, `locales/es.json` (ex: `commands.myCommand.success`).
|
||||
4. Ajouter les clés de traduction dans `src/i18n/en.json`, `src/i18n/fr.json`, `src/i18n/es.json` (ex: `commands.myCommand.success`).
|
||||
5. Écrire des tests unitaires dans `tests/` pour le service/manager; si la commande est juste un wrapper, testez le service.
|
||||
6. Si c'est une slash command, vérifier que `scripts/deployCommands.ts`/le registre inclut la commande; exécuter le déploiement si nécessaire.
|
||||
6. Si c'est une slash command, vérifier que le registre inclut la commande; redémarrer le bot avec `AUTO_DEPLOY_SLASH=true` si synchronisation nécessaire.
|
||||
7. Lancer `npm run build` puis `npm test` (ou la suite de scripts définie dans `package.json`).
|
||||
8. Ouvrir une PR documentant le changement et incluant les tests et les traductions.
|
||||
|
||||
@@ -75,7 +72,7 @@ Tests et CI
|
||||
Déploiement / exécution
|
||||
-----------------------
|
||||
- Utiliser `Dockerfile` / `docker-compose.yml` fournis pour le déploiement en conteneur.
|
||||
- Pour les slash commands, exécuter `scripts/deployCommands.ts` (ou le script npm associé).
|
||||
- Pour les slash commands, activer `AUTO_DEPLOY_SLASH=true` pour une synchronisation automatique au démarrage.
|
||||
|
||||
Revue et PR
|
||||
-----------
|
||||
|
||||
+3
-4
@@ -6,8 +6,8 @@ COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY tsconfig.json ./
|
||||
COPY scripts ./scripts
|
||||
COPY src ./src
|
||||
COPY locales ./locales
|
||||
|
||||
RUN npm run build
|
||||
|
||||
@@ -19,7 +19,6 @@ ENV NODE_ENV=production
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY locales ./locales
|
||||
COPY --from=builder /app/build ./build
|
||||
|
||||
CMD ["node", "dist/index.js"]
|
||||
CMD ["node", "build/index.js"]
|
||||
|
||||
@@ -32,11 +32,11 @@ Professional command framework template for Discord.js `14.26.2` with:
|
||||
4. Optional values:
|
||||
- `DATABASE_SSL` (`true` for managed cloud DB, `false` for local Docker)
|
||||
- `PRESENCE_STREAM_URL` (used when activity type is `STREAMING`)
|
||||
5. Deploy slash commands:
|
||||
npm run deploy:commands
|
||||
6. Start in development:
|
||||
- `AUTO_DEPLOY_SLASH` (`true` to sync slash commands on startup)
|
||||
- `DEV_GUILD_ID` (optional guild scope for faster slash sync)
|
||||
5. Start in development:
|
||||
npm run dev
|
||||
7. Validate code quality:
|
||||
6. Validate code quality:
|
||||
- npm run typecheck
|
||||
- npm run test
|
||||
- npm run check
|
||||
@@ -81,14 +81,14 @@ An example with two bot services is available in `docker-compose.multi-bot.examp
|
||||
|
||||
- `src/framework/types/command.ts`: strict command schema
|
||||
- `src/framework/commands/defineCommand.ts`: default command completion
|
||||
- `src/framework/commands/registry.ts`: trigger/name mapping generated from locales
|
||||
- `src/framework/commands/registry.ts`: trigger/name mapping generated from i18n dictionaries
|
||||
- `src/framework/commands/argParser.ts`: prefix/slash args parsing from schema
|
||||
- `src/framework/execution/CommandExecutor.ts`: unified pipeline (permissions/cooldown/execute)
|
||||
- `src/framework/handlers/prefixHandler.ts`: prefix entrypoint
|
||||
- `src/framework/handlers/slashHandler.ts`: slash entrypoint
|
||||
- `src/framework/presence/presenceStore.ts`: PostgreSQL presence storage
|
||||
- `src/framework/presence/presenceTypes.ts`: shared presence types/validation
|
||||
- `locales/*.json`: external i18n dictionaries
|
||||
- `src/i18n/*.json`: external i18n dictionaries
|
||||
- `src/commands/*`: business commands only (`execute`, optional `cooldown`)
|
||||
|
||||
## Included Commands
|
||||
@@ -104,4 +104,4 @@ An example with two bot services is available in `docker-compose.multi-bot.examp
|
||||
1. Create a command object in `src/commands/...`
|
||||
2. Follow the schema in `src/framework/types/command.ts`
|
||||
3. Add command to `src/commands/index.ts`
|
||||
4. Deploy slash commands again (`npm run deploy:commands`)
|
||||
4. If `AUTO_DEPLOY_SLASH=true`, restart the bot to sync slash commands automatically
|
||||
|
||||
Generated
+584
-99
@@ -17,6 +17,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "20.17.24",
|
||||
"@types/pg": "^8.20.0",
|
||||
"esbuild": "^0.25.12",
|
||||
"tsx": "4.19.2",
|
||||
"typescript": "5.8.2"
|
||||
},
|
||||
@@ -165,9 +166,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
|
||||
"integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
|
||||
"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==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -182,9 +183,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
|
||||
"integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
|
||||
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -199,9 +200,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
|
||||
"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==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -216,9 +217,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
|
||||
"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==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -233,9 +234,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -250,9 +251,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -267,9 +268,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -284,9 +285,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -301,9 +302,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
|
||||
"integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
|
||||
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -318,9 +319,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -335,9 +336,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
|
||||
"integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
|
||||
"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==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -352,9 +353,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
|
||||
"integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
|
||||
"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==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -369,9 +370,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
|
||||
"integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
|
||||
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -386,9 +387,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
|
||||
"integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
|
||||
"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==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -403,9 +404,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
|
||||
"integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
|
||||
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -420,9 +421,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
|
||||
"integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
|
||||
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -437,9 +438,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -453,10 +454,27 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -471,9 +489,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -488,9 +506,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -504,10 +522,27 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -522,9 +557,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -539,9 +574,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
|
||||
"integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
|
||||
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -556,9 +591,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -943,9 +978,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
|
||||
"integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
||||
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -956,30 +991,32 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.23.1",
|
||||
"@esbuild/android-arm": "0.23.1",
|
||||
"@esbuild/android-arm64": "0.23.1",
|
||||
"@esbuild/android-x64": "0.23.1",
|
||||
"@esbuild/darwin-arm64": "0.23.1",
|
||||
"@esbuild/darwin-x64": "0.23.1",
|
||||
"@esbuild/freebsd-arm64": "0.23.1",
|
||||
"@esbuild/freebsd-x64": "0.23.1",
|
||||
"@esbuild/linux-arm": "0.23.1",
|
||||
"@esbuild/linux-arm64": "0.23.1",
|
||||
"@esbuild/linux-ia32": "0.23.1",
|
||||
"@esbuild/linux-loong64": "0.23.1",
|
||||
"@esbuild/linux-mips64el": "0.23.1",
|
||||
"@esbuild/linux-ppc64": "0.23.1",
|
||||
"@esbuild/linux-riscv64": "0.23.1",
|
||||
"@esbuild/linux-s390x": "0.23.1",
|
||||
"@esbuild/linux-x64": "0.23.1",
|
||||
"@esbuild/netbsd-x64": "0.23.1",
|
||||
"@esbuild/openbsd-arm64": "0.23.1",
|
||||
"@esbuild/openbsd-x64": "0.23.1",
|
||||
"@esbuild/sunos-x64": "0.23.1",
|
||||
"@esbuild/win32-arm64": "0.23.1",
|
||||
"@esbuild/win32-ia32": "0.23.1",
|
||||
"@esbuild/win32-x64": "0.23.1"
|
||||
"@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"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
@@ -1214,6 +1251,454 @@
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
|
||||
"integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/android-arm": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
|
||||
"integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/android-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
|
||||
"integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
|
||||
"integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
|
||||
"integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
|
||||
"integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
|
||||
"integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
|
||||
"integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
|
||||
"integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
|
||||
"integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/esbuild": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
|
||||
"integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.23.1",
|
||||
"@esbuild/android-arm": "0.23.1",
|
||||
"@esbuild/android-arm64": "0.23.1",
|
||||
"@esbuild/android-x64": "0.23.1",
|
||||
"@esbuild/darwin-arm64": "0.23.1",
|
||||
"@esbuild/darwin-x64": "0.23.1",
|
||||
"@esbuild/freebsd-arm64": "0.23.1",
|
||||
"@esbuild/freebsd-x64": "0.23.1",
|
||||
"@esbuild/linux-arm": "0.23.1",
|
||||
"@esbuild/linux-arm64": "0.23.1",
|
||||
"@esbuild/linux-ia32": "0.23.1",
|
||||
"@esbuild/linux-loong64": "0.23.1",
|
||||
"@esbuild/linux-mips64el": "0.23.1",
|
||||
"@esbuild/linux-ppc64": "0.23.1",
|
||||
"@esbuild/linux-riscv64": "0.23.1",
|
||||
"@esbuild/linux-s390x": "0.23.1",
|
||||
"@esbuild/linux-x64": "0.23.1",
|
||||
"@esbuild/netbsd-x64": "0.23.1",
|
||||
"@esbuild/openbsd-arm64": "0.23.1",
|
||||
"@esbuild/openbsd-x64": "0.23.1",
|
||||
"@esbuild/sunos-x64": "0.23.1",
|
||||
"@esbuild/win32-arm64": "0.23.1",
|
||||
"@esbuild/win32-ia32": "0.23.1",
|
||||
"@esbuild/win32-x64": "0.23.1"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
||||
|
||||
+4
-4
@@ -8,12 +8,11 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"build": "npm run typecheck && node scripts/build.mjs",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"test": "node --import tsx --test src/**/*.test.ts",
|
||||
"test": "node --import tsx --test tests/**/*.test.ts",
|
||||
"check": "npm run typecheck && npm run test",
|
||||
"start": "node dist/index.js",
|
||||
"deploy:commands": "tsx src/scripts/deployCommands.ts"
|
||||
"start": "node build/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@napi-rs/canvas": "^0.1.97",
|
||||
@@ -25,6 +24,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "20.17.24",
|
||||
"@types/pg": "^8.20.0",
|
||||
"esbuild": "^0.25.12",
|
||||
"tsx": "4.19.2",
|
||||
"typescript": "5.8.2"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { cp, mkdir, readdir, rm } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { build } from "esbuild";
|
||||
|
||||
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT_DIR = path.resolve(SCRIPT_DIR, "..");
|
||||
const OUTPUT_DIR = path.join(ROOT_DIR, "build");
|
||||
const SOURCE_I18N_DIR = path.join(ROOT_DIR, "src", "i18n");
|
||||
const OUTPUT_I18N_DIR = path.join(OUTPUT_DIR, "i18n");
|
||||
|
||||
const main = async () => {
|
||||
await rm(OUTPUT_DIR, { recursive: true, force: true });
|
||||
|
||||
await build({
|
||||
absWorkingDir: ROOT_DIR,
|
||||
entryPoints: ["src/index.ts"],
|
||||
outfile: "build/index.js",
|
||||
bundle: true,
|
||||
packages: "external",
|
||||
minify: true,
|
||||
legalComments: "none",
|
||||
platform: "node",
|
||||
format: "esm",
|
||||
target: ["node18"],
|
||||
tsconfig: "tsconfig.json",
|
||||
logLevel: "info",
|
||||
});
|
||||
|
||||
await mkdir(OUTPUT_I18N_DIR, { recursive: true });
|
||||
const localeEntries = await readdir(SOURCE_I18N_DIR, { withFileTypes: true });
|
||||
for (const entry of localeEntries) {
|
||||
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await cp(path.join(SOURCE_I18N_DIR, entry.name), path.join(OUTPUT_I18N_DIR, entry.name), { force: true });
|
||||
}
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("[build] failed", error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
} from "discord.js";
|
||||
|
||||
import { env } from "../framework/config/env.js";
|
||||
import { I18nService } from "../framework/i18n/I18nService.js";
|
||||
import { I18nService } from "../i18n/index.js";
|
||||
import { dispatchMemberMessage } from "../framework/memberMessages/memberMessageSender.js";
|
||||
import { getMemberMessageStore } from "../framework/memberMessages/memberMessageStore.js";
|
||||
import {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Events, type Client } from "discord.js";
|
||||
import type { I18nService } from "../framework/i18n/I18nService.js";
|
||||
import type { I18nService } from "../i18n/index.js";
|
||||
import { dispatchMemberMessage } from "../framework/memberMessages/memberMessageSender.js";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Events, type Client } from "discord.js";
|
||||
import type { I18nService } from "../framework/i18n/I18nService.js";
|
||||
import type { I18nService } from "../i18n/index.js";
|
||||
import { dispatchMemberMessage } from "../framework/memberMessages/memberMessageSender.js";
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import type { Client, Message, ChatInputCommandInteraction } from "discord.js";
|
||||
import type { I18nService } from "../framework/i18n/I18nService.js";
|
||||
import type { I18nService } from "../i18n/index.js";
|
||||
|
||||
import { registerMessageCreate } from "./messageCreate.js";
|
||||
import { registerInteractionCreate } from "./interactionCreate.js";
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { deployApplicationCommands } from "../framework/commands/deploy.js";
|
||||
import { env } from "../framework/config/env.js";
|
||||
import { restorePresenceFromStorage } from "../commands/presence.js";
|
||||
import type { CommandRegistry } from "../framework/commands/registry.js";
|
||||
import type { I18nService } from "../framework/i18n/I18nService.js";
|
||||
import type { I18nService } from "../i18n/index.js";
|
||||
|
||||
/**
|
||||
* Attache le listener `ready` et exécute les tâches post-démarrage:
|
||||
|
||||
@@ -2,7 +2,7 @@ import { REST, Routes } from "discord.js";
|
||||
|
||||
import { buildSlashPayload } from "./slashBuilder.js";
|
||||
import type { CommandRegistry } from "./registry.js";
|
||||
import type { I18nService } from "../i18n/I18nService.js";
|
||||
import type { I18nService } from "../../i18n/index.js";
|
||||
|
||||
export interface DeployCommandsOptions {
|
||||
token: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { BotCommand, PrefixTriggerMatch } from "../types/command.js";
|
||||
import { SUPPORTED_LANGS } from "../types/command.js";
|
||||
import type { I18nService } from "../i18n/I18nService.js";
|
||||
import type { I18nService } from "../../i18n/index.js";
|
||||
|
||||
const normalize = (value: string): string => value.trim().toLowerCase();
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from "discord.js";
|
||||
|
||||
import type { BotCommand, CommandArgument, SupportedLang } from "../types/command.js";
|
||||
import type { I18nService } from "../i18n/I18nService.js";
|
||||
import type { I18nService } from "../../i18n/index.js";
|
||||
|
||||
const LANG_TO_DISCORD_LOCALE: Record<SupportedLang, string> = {
|
||||
en: "en-US",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CommandRegistry } from "../commands/registry.js";
|
||||
import type { I18nService } from "../i18n/I18nService.js";
|
||||
import type { I18nService } from "../../i18n/index.js";
|
||||
import type {
|
||||
BotCommand,
|
||||
CommandExecutionContext,
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { SUPPORTED_LANGS, type SupportedLang, type TranslationVars } from "../types/command.js";
|
||||
|
||||
type JsonObject = Record<string, unknown>;
|
||||
|
||||
const DISCORD_LOCALE_MAP: Record<string, SupportedLang> = {
|
||||
en: "en",
|
||||
"en-us": "en",
|
||||
"en-gb": "en",
|
||||
fr: "fr",
|
||||
"fr-fr": "fr",
|
||||
es: "es",
|
||||
"es-es": "es",
|
||||
"es-419": "es",
|
||||
};
|
||||
|
||||
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||
const LOCALES_DIR = path.resolve(CURRENT_DIR, "../../../locales");
|
||||
|
||||
export class I18nService {
|
||||
private readonly dictionaries: Record<SupportedLang, JsonObject>;
|
||||
|
||||
public constructor(private readonly defaultLang: SupportedLang) {
|
||||
this.dictionaries = this.loadDictionaries();
|
||||
}
|
||||
|
||||
public resolveLang(input?: string | null): SupportedLang {
|
||||
if (!input) {
|
||||
return this.defaultLang;
|
||||
}
|
||||
|
||||
const normalized = input.toLowerCase();
|
||||
|
||||
const direct = DISCORD_LOCALE_MAP[normalized];
|
||||
if (direct) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
const short = normalized.split("-")[0];
|
||||
const fromShort = short ? DISCORD_LOCALE_MAP[short] : undefined;
|
||||
if (fromShort) {
|
||||
return fromShort;
|
||||
}
|
||||
|
||||
return this.defaultLang;
|
||||
}
|
||||
|
||||
public t(lang: SupportedLang, key: string, vars: TranslationVars = {}): string {
|
||||
const fromLang = this.lookup(this.dictionaries[lang], key);
|
||||
const fromDefault = this.lookup(this.dictionaries[this.defaultLang], key);
|
||||
|
||||
const template = typeof fromLang === "string" ? fromLang : typeof fromDefault === "string" ? fromDefault : key;
|
||||
|
||||
return this.format(template, vars);
|
||||
}
|
||||
|
||||
public commandT(lang: SupportedLang, commandName: string, relativeKey: string, vars: TranslationVars = {}): string {
|
||||
return this.t(lang, `${this.commandBaseKey(commandName)}.${relativeKey}`, vars);
|
||||
}
|
||||
|
||||
public commandName(lang: SupportedLang, commandName: string): string {
|
||||
const key = `${this.commandBaseKey(commandName)}.name`;
|
||||
const fromLang = this.lookup(this.dictionaries[lang], key);
|
||||
if (typeof fromLang === "string" && fromLang.length > 0) {
|
||||
return fromLang;
|
||||
}
|
||||
|
||||
const fromDefault = this.lookup(this.dictionaries[this.defaultLang], key);
|
||||
if (typeof fromDefault === "string" && fromDefault.length > 0) {
|
||||
return fromDefault;
|
||||
}
|
||||
|
||||
return commandName;
|
||||
}
|
||||
|
||||
public commandTrigger(lang: SupportedLang, commandName: string): string {
|
||||
return this.commandName(lang, commandName).trim().toLowerCase();
|
||||
}
|
||||
|
||||
public commandObject(lang: SupportedLang, commandName: string): Record<string, unknown> {
|
||||
const key = this.commandBaseKey(commandName);
|
||||
const fromLang = this.lookup(this.dictionaries[lang], key);
|
||||
if (this.isObject(fromLang)) {
|
||||
return fromLang;
|
||||
}
|
||||
|
||||
const fromDefault = this.lookup(this.dictionaries[this.defaultLang], key);
|
||||
if (this.isObject(fromDefault)) {
|
||||
return fromDefault;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
public format(template: string, vars: TranslationVars = {}): string {
|
||||
return this.interpolate(template, vars);
|
||||
}
|
||||
|
||||
private loadDictionaries(): Record<SupportedLang, JsonObject> {
|
||||
return SUPPORTED_LANGS.reduce<Record<SupportedLang, JsonObject>>((acc, lang) => {
|
||||
const filePath = path.join(LOCALES_DIR, `${lang}.json`);
|
||||
const raw = readFileSync(filePath, "utf-8");
|
||||
acc[lang] = JSON.parse(raw) as JsonObject;
|
||||
return acc;
|
||||
}, {} as Record<SupportedLang, JsonObject>);
|
||||
}
|
||||
|
||||
private lookup(source: JsonObject, key: string): unknown {
|
||||
const parts = key.split(".");
|
||||
let current: unknown = source;
|
||||
|
||||
for (const part of parts) {
|
||||
if (!current || typeof current !== "object" || Array.isArray(current)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
current = (current as JsonObject)[part];
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
private commandBaseKey(commandName: string): string {
|
||||
return `commands.${commandName}`;
|
||||
}
|
||||
|
||||
private isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
private interpolate(template: string, vars: TranslationVars): string {
|
||||
return template.replace(/\{\{(\w+)\}\}/g, (_, variable: string) => {
|
||||
const value = vars[variable];
|
||||
return value === undefined || value === null ? "" : String(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
type User,
|
||||
} from "discord.js";
|
||||
|
||||
import type { I18nService } from "../i18n/I18nService.js";
|
||||
import type { I18nService } from "../../i18n/index.js";
|
||||
import type { SupportedLang } from "../types/command.js";
|
||||
import { renderMemberMessageImage } from "./memberMessageImage.js";
|
||||
import { getMemberMessageStore } from "./memberMessageStore.js";
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { SUPPORTED_LANGS, type SupportedLang, type TranslationVars } from "../framework/types/command.js";
|
||||
|
||||
type JsonObject = Record<string, unknown>;
|
||||
|
||||
const DISCORD_LOCALE_MAP: Record<string, SupportedLang> = {
|
||||
en: "en",
|
||||
"en-us": "en",
|
||||
"en-gb": "en",
|
||||
fr: "fr",
|
||||
"fr-fr": "fr",
|
||||
es: "es",
|
||||
"es-es": "es",
|
||||
"es-419": "es",
|
||||
};
|
||||
|
||||
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||
const LOCALE_DIR_CANDIDATES = [
|
||||
CURRENT_DIR,
|
||||
path.resolve(process.cwd(), "src", "i18n"),
|
||||
path.resolve(process.cwd(), "build", "i18n"),
|
||||
];
|
||||
|
||||
const resolveLocaleFilePath = (lang: SupportedLang): string => {
|
||||
for (const directory of LOCALE_DIR_CANDIDATES) {
|
||||
const filePath = path.join(directory, `${lang}.json`);
|
||||
if (existsSync(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`[i18n] missing locale file for "${lang}"`);
|
||||
};
|
||||
|
||||
export class I18nService {
|
||||
private readonly dictionaries: Record<SupportedLang, JsonObject>;
|
||||
|
||||
public constructor(private readonly defaultLang: SupportedLang) {
|
||||
this.dictionaries = this.loadDictionaries();
|
||||
}
|
||||
|
||||
public resolveLang(input?: string | null): SupportedLang {
|
||||
if (!input) {
|
||||
return this.defaultLang;
|
||||
}
|
||||
|
||||
const normalized = input.toLowerCase();
|
||||
|
||||
const direct = DISCORD_LOCALE_MAP[normalized];
|
||||
if (direct) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
const short = normalized.split("-")[0];
|
||||
const fromShort = short ? DISCORD_LOCALE_MAP[short] : undefined;
|
||||
if (fromShort) {
|
||||
return fromShort;
|
||||
}
|
||||
|
||||
return this.defaultLang;
|
||||
}
|
||||
|
||||
public t(lang: SupportedLang, key: string, vars: TranslationVars = {}): string {
|
||||
const fromLang = this.lookup(this.dictionaries[lang], key);
|
||||
const fromDefault = this.lookup(this.dictionaries[this.defaultLang], key);
|
||||
|
||||
const template = typeof fromLang === "string" ? fromLang : typeof fromDefault === "string" ? fromDefault : key;
|
||||
|
||||
return this.format(template, vars);
|
||||
}
|
||||
|
||||
public commandT(lang: SupportedLang, commandName: string, relativeKey: string, vars: TranslationVars = {}): string {
|
||||
return this.t(lang, `${this.commandBaseKey(commandName)}.${relativeKey}`, vars);
|
||||
}
|
||||
|
||||
public commandName(lang: SupportedLang, commandName: string): string {
|
||||
const key = `${this.commandBaseKey(commandName)}.name`;
|
||||
const fromLang = this.lookup(this.dictionaries[lang], key);
|
||||
if (typeof fromLang === "string" && fromLang.length > 0) {
|
||||
return fromLang;
|
||||
}
|
||||
|
||||
const fromDefault = this.lookup(this.dictionaries[this.defaultLang], key);
|
||||
if (typeof fromDefault === "string" && fromDefault.length > 0) {
|
||||
return fromDefault;
|
||||
}
|
||||
|
||||
return commandName;
|
||||
}
|
||||
|
||||
public commandTrigger(lang: SupportedLang, commandName: string): string {
|
||||
return this.commandName(lang, commandName).trim().toLowerCase();
|
||||
}
|
||||
|
||||
public commandObject(lang: SupportedLang, commandName: string): Record<string, unknown> {
|
||||
const key = this.commandBaseKey(commandName);
|
||||
const fromLang = this.lookup(this.dictionaries[lang], key);
|
||||
if (this.isObject(fromLang)) {
|
||||
return fromLang;
|
||||
}
|
||||
|
||||
const fromDefault = this.lookup(this.dictionaries[this.defaultLang], key);
|
||||
if (this.isObject(fromDefault)) {
|
||||
return fromDefault;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
public format(template: string, vars: TranslationVars = {}): string {
|
||||
return this.interpolate(template, vars);
|
||||
}
|
||||
|
||||
private loadDictionaries(): Record<SupportedLang, JsonObject> {
|
||||
return SUPPORTED_LANGS.reduce<Record<SupportedLang, JsonObject>>((acc, lang) => {
|
||||
const filePath = resolveLocaleFilePath(lang);
|
||||
const raw = readFileSync(filePath, "utf-8");
|
||||
acc[lang] = JSON.parse(raw) as JsonObject;
|
||||
return acc;
|
||||
}, {} as Record<SupportedLang, JsonObject>);
|
||||
}
|
||||
|
||||
private lookup(source: JsonObject, key: string): unknown {
|
||||
const parts = key.split(".");
|
||||
let current: unknown = source;
|
||||
|
||||
for (const part of parts) {
|
||||
if (!current || typeof current !== "object" || Array.isArray(current)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
current = (current as JsonObject)[part];
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
private commandBaseKey(commandName: string): string {
|
||||
return `commands.${commandName}`;
|
||||
}
|
||||
|
||||
private isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
private interpolate(template: string, vars: TranslationVars): string {
|
||||
return template.replace(/\{\{(\w+)\}\}/g, (_, variable: string) => {
|
||||
const value = vars[variable];
|
||||
return value === undefined || value === null ? "" : String(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@ import { env } from "./framework/config/env.js";
|
||||
import { CommandExecutor } from "./framework/execution/CommandExecutor.js";
|
||||
import { createPrefixHandler } from "./framework/handlers/prefixHandler.js";
|
||||
import { createSlashHandler } from "./framework/handlers/slashHandler.js";
|
||||
import { I18nService } from "./framework/i18n/I18nService.js";
|
||||
import { I18nService } from "./i18n/index.js";
|
||||
import {
|
||||
initMemberMessageStore,
|
||||
shutdownMemberMessageStore,
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { commandList } from "../commands/index.js";
|
||||
import { deployApplicationCommands } from "../framework/commands/deploy.js";
|
||||
import { CommandRegistry } from "../framework/commands/registry.js";
|
||||
import { env } from "../framework/config/env.js";
|
||||
import { I18nService } from "../framework/i18n/I18nService.js";
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
const i18n = new I18nService(env.DEFAULT_LANG);
|
||||
const registry = new CommandRegistry(commandList, i18n);
|
||||
|
||||
const result = await deployApplicationCommands({
|
||||
token: env.DISCORD_TOKEN,
|
||||
clientId: env.DISCORD_CLIENT_ID,
|
||||
registry,
|
||||
i18n,
|
||||
...(env.DEV_GUILD_ID ? { guildId: env.DEV_GUILD_ID } : {}),
|
||||
});
|
||||
|
||||
console.log(`[deploy] ${result.count} commands deployed (${result.scope})`);
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("[deploy] failed", error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { tokenizePrefixInput } from "../framework/commands/argParser.js";
|
||||
import { tokenizePrefixInput } from "../src/framework/commands/argParser.js";
|
||||
|
||||
test("tokenizePrefixInput parse les quotes simples et doubles", () => {
|
||||
const tokens = tokenizePrefixInput('"hello world" test \'foo bar\'');
|
||||
@@ -14,6 +14,6 @@ test("tokenizePrefixInput conserve les tokens non quotes", () => {
|
||||
});
|
||||
|
||||
test("tokenizePrefixInput gere les quotes echappees", () => {
|
||||
const tokens = tokenizePrefixInput('"say \\\"hello\\\"" done');
|
||||
const tokens = tokenizePrefixInput('"say \\"hello\\"" done');
|
||||
assert.deepEqual(tokens, ['say "hello"', "done"]);
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { defineCommand } from "../framework/commands/defineCommand.js";
|
||||
import { defineCommand } from "../src/framework/commands/defineCommand.js";
|
||||
|
||||
test("defineCommand refuse un argument requis apres un optionnel", () => {
|
||||
assert.throws(() => {
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
parsePresenceState,
|
||||
sanitizeActivityTexts,
|
||||
sanitizePresenceRotationIntervalSeconds,
|
||||
} from "../framework/presence/presenceTypes.js";
|
||||
} from "../src/framework/presence/presenceTypes.js";
|
||||
|
||||
test("sanitizeActivityTexts fallback sur le texte par defaut", () => {
|
||||
const texts = sanitizeActivityTexts([" ", ""]);
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
extractTemplateVariables,
|
||||
hasTemplateVariable,
|
||||
renderTemplate,
|
||||
} from "../framework/utils/templateVariables.js";
|
||||
} from "../src/framework/utils/templateVariables.js";
|
||||
|
||||
test("renderTemplate remplace les variables et applique les alias", () => {
|
||||
const output = renderTemplate(
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"outDir": "build",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
Reference in New Issue
Block a user