extract_picture

This commit is contained in:
Ladebeze66 2026-04-28 12:22:24 +02:00
parent 13b4b6971c
commit d423372251
12 changed files with 1417 additions and 108 deletions

3
.gitignore vendored
View File

@ -55,3 +55,6 @@ llm-api/*.pyc
# Legacy RAG index (ChromaDB) — obsolete depuis bascule graph+BM25 # Legacy RAG index (ChromaDB) — obsolete depuis bascule graph+BM25
/chroma-index/ /chroma-index/
# Téléchargements + WebP générés par strapi_extraction/media-sync/ (poids important)
strapi_extraction/extract/media-sync-work/

View File

@ -6,6 +6,7 @@ import { fetchData } from "../utils/fetchData";
import { getApiUrl } from "../utils/getApiUrl"; import { getApiUrl } from "../utils/getApiUrl";
import Carousel from "./Carousel"; import Carousel from "./Carousel";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
interface ImageData { interface ImageData {
url: string; url: string;
@ -211,7 +212,7 @@ export default function ContentSection({
prose-li:marker:text-primary prose-li:marker:text-primary
prose-hr:border-0 prose-hr:w-16 prose-hr:mx-auto prose-hr:bg-primary/30 prose-hr:h-0.5 prose-hr:rounded-full prose-hr:my-6" prose-hr:border-0 prose-hr:w-16 prose-hr:mx-auto prose-hr:bg-primary/30 prose-hr:h-0.5 prose-hr:rounded-full prose-hr:my-6"
> >
<ReactMarkdown>{richText}</ReactMarkdown> <ReactMarkdown remarkPlugins={[remarkGfm]}>{richText}</ReactMarkdown>
</div> </div>
)} )}

View File

@ -1,6 +1,6 @@
# Outils `strapi_extraction/` # Outils `strapi_extraction/`
**Dernière mise à jour :** 2026-04-22 **Dernière mise à jour :** 2026-04-28
Dossier de **scripts Node + Python** pour extraire, nettoyer et convertir les Dossier de **scripts Node + Python** pour extraire, nettoyer et convertir les
données issues de l'API Strapi en base de connaissance chatbot (hors runtime données issues de l'API Strapi en base de connaissance chatbot (hors runtime
@ -84,8 +84,18 @@ comme source de vérité sans comparer au CMS.
Ces points seront corrigés en même temps que l'enrichissement du vault Ces points seront corrigés en même temps que l'enrichissement du vault
(glossaire + homepage Strapi → notes `40-Glossaire/` et `30-Parcours/`). (glossaire + homepage Strapi → notes `40-Glossaire/` et `30-Parcours/`).
## Sync médias WebP (hors pipeline GrasBot)
Dossier **`strapi_extraction/media-sync/`** — inventaire des fichiers image liés aux
content-types (`projects`, `competences`, `homepages`, `realisation-ias`, `glossaires`),
téléchargement classé par rubrique, conversion WebP (sharp), puis ré-upload optionnel.
Documentation : voir `strapi_extraction/media-sync/README.md`.
Sortie lourde (ignorée Git) : `strapi_extraction/extract/media-sync-work/`.
## Liens complémentaires ## Liens complémentaires
- Vault + retrieval : [`08-vault-obsidian-retrieval.md`](./08-vault-obsidian-retrieval.md) - Vault + retrieval : [`08-vault-obsidian-retrieval.md`](./08-vault-obsidian-retrieval.md)
- API LLM : [`04-api-llm-et-chatbot.md`](./04-api-llm-et-chatbot.md) - API LLM : [`04-api-llm-et-chatbot.md`](./04-api-llm-et-chatbot.md)
- Schémas Strapi : [`03-cms-strapi.md`](./03-cms-strapi.md) - Schémas Strapi : [`03-cms-strapi.md`](./03-cms-strapi.md)
- Performances images (audit) : [`09-performances-images.md`](./09-performances-images.md)

673
package-lock.json generated
View File

@ -30,6 +30,7 @@
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"postcss": "^8", "postcss": "^8",
"sharp": "^0.33.5",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5" "typescript": "^5"
} }
@ -47,9 +48,9 @@
} }
}, },
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.8.1", "version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@ -57,9 +58,9 @@
} }
}, },
"node_modules/@img/colour": { "node_modules/@img/colour": {
"version": "1.0.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"engines": { "engines": {
@ -67,12 +68,13 @@
} }
}, },
"node_modules/@img/sharp-darwin-arm64": { "node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -85,16 +87,17 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.4" "@img/sharp-libvips-darwin-arm64": "1.0.4"
} }
}, },
"node_modules/@img/sharp-darwin-x64": { "node_modules/@img/sharp-darwin-x64": {
"version": "0.34.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -107,16 +110,17 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.4" "@img/sharp-libvips-darwin-x64": "1.0.4"
} }
}, },
"node_modules/@img/sharp-libvips-darwin-arm64": { "node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.2.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -127,12 +131,13 @@
} }
}, },
"node_modules/@img/sharp-libvips-darwin-x64": { "node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.2.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -143,12 +148,13 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-arm": { "node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.2.4", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -159,12 +165,13 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-arm64": { "node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.2.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -207,12 +214,13 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-s390x": { "node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.2.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -223,12 +231,13 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-x64": { "node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.2.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -239,12 +248,13 @@
} }
}, },
"node_modules/@img/sharp-libvips-linuxmusl-arm64": { "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.2.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -255,12 +265,13 @@
} }
}, },
"node_modules/@img/sharp-libvips-linuxmusl-x64": { "node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.2.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -271,12 +282,13 @@
} }
}, },
"node_modules/@img/sharp-linux-arm": { "node_modules/@img/sharp-linux-arm": {
"version": "0.34.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -289,16 +301,17 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.4" "@img/sharp-libvips-linux-arm": "1.0.5"
} }
}, },
"node_modules/@img/sharp-linux-arm64": { "node_modules/@img/sharp-linux-arm64": {
"version": "0.34.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -311,7 +324,7 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.4" "@img/sharp-libvips-linux-arm64": "1.0.4"
} }
}, },
"node_modules/@img/sharp-linux-ppc64": { "node_modules/@img/sharp-linux-ppc64": {
@ -359,12 +372,13 @@
} }
}, },
"node_modules/@img/sharp-linux-s390x": { "node_modules/@img/sharp-linux-s390x": {
"version": "0.34.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -377,16 +391,17 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.2.4" "@img/sharp-libvips-linux-s390x": "1.0.4"
} }
}, },
"node_modules/@img/sharp-linux-x64": { "node_modules/@img/sharp-linux-x64": {
"version": "0.34.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -399,16 +414,17 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.4" "@img/sharp-libvips-linux-x64": "1.0.4"
} }
}, },
"node_modules/@img/sharp-linuxmusl-arm64": { "node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -421,16 +437,17 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4" "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
} }
}, },
"node_modules/@img/sharp-linuxmusl-x64": { "node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -443,20 +460,21 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.4" "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
} }
}, },
"node_modules/@img/sharp-wasm32": { "node_modules/@img/sharp-wasm32": {
"version": "0.34.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
"cpu": [ "cpu": [
"wasm32" "wasm32"
], ],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@emnapi/runtime": "^1.7.0" "@emnapi/runtime": "^1.2.0"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@ -485,12 +503,13 @@
} }
}, },
"node_modules/@img/sharp-win32-ia32": { "node_modules/@img/sharp-win32-ia32": {
"version": "0.34.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later", "license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -504,12 +523,13 @@
} }
}, },
"node_modules/@img/sharp-win32-x64": { "node_modules/@img/sharp-win32-x64": {
"version": "0.34.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later", "license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -1164,6 +1184,20 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -1182,6 +1216,17 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/comma-separated-tokens": { "node_modules/comma-separated-tokens": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
@ -1276,8 +1321,8 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -1860,6 +1905,13 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/is-arrayish": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"dev": true,
"license": "MIT"
},
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -3058,6 +3110,367 @@
} }
} }
}, },
"node_modules/next/node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.4"
}
},
"node_modules/next/node_modules/@img/sharp-darwin-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.4"
}
},
"node_modules/next/node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/next/node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/next/node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
"cpu": [
"arm"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/next/node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/next/node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
"cpu": [
"s390x"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/next/node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/next/node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/next/node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/next/node_modules/@img/sharp-linux-arm": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.4"
}
},
"node_modules/next/node_modules/@img/sharp-linux-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.4"
}
},
"node_modules/next/node_modules/@img/sharp-linux-s390x": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
"cpu": [
"s390x"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.2.4"
}
},
"node_modules/next/node_modules/@img/sharp-linux-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.4"
}
},
"node_modules/next/node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
}
},
"node_modules/next/node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
}
},
"node_modules/next/node_modules/@img/sharp-wasm32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.7.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/next/node_modules/@img/sharp-win32-ia32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/next/node_modules/@img/sharp-win32-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/next/node_modules/postcss": { "node_modules/next/node_modules/postcss": {
"version": "8.4.31", "version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@ -3086,6 +3499,51 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/next/node_modules/sharp": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@img/colour": "^1.0.0",
"detect-libc": "^2.1.2",
"semver": "^7.7.3"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.5",
"@img/sharp-darwin-x64": "0.34.5",
"@img/sharp-libvips-darwin-arm64": "1.2.4",
"@img/sharp-libvips-darwin-x64": "1.2.4",
"@img/sharp-libvips-linux-arm": "1.2.4",
"@img/sharp-libvips-linux-arm64": "1.2.4",
"@img/sharp-libvips-linux-ppc64": "1.2.4",
"@img/sharp-libvips-linux-riscv64": "1.2.4",
"@img/sharp-libvips-linux-s390x": "1.2.4",
"@img/sharp-libvips-linux-x64": "1.2.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
"@img/sharp-linux-arm": "0.34.5",
"@img/sharp-linux-arm64": "0.34.5",
"@img/sharp-linux-ppc64": "0.34.5",
"@img/sharp-linux-riscv64": "0.34.5",
"@img/sharp-linux-s390x": "0.34.5",
"@img/sharp-linux-x64": "0.34.5",
"@img/sharp-linuxmusl-arm64": "0.34.5",
"@img/sharp-linuxmusl-x64": "0.34.5",
"@img/sharp-wasm32": "0.34.5",
"@img/sharp-win32-arm64": "0.34.5",
"@img/sharp-win32-ia32": "0.34.5",
"@img/sharp-win32-x64": "0.34.5"
}
},
"node_modules/normalize-path": { "node_modules/normalize-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@ -3713,8 +4171,8 @@
"version": "7.7.3", "version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"optional": true,
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
}, },
@ -3723,16 +4181,16 @@
} }
}, },
"node_modules/sharp": { "node_modules/sharp": {
"version": "0.34.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true,
"dependencies": { "dependencies": {
"@img/colour": "^1.0.0", "color": "^4.2.3",
"detect-libc": "^2.1.2", "detect-libc": "^2.0.3",
"semver": "^7.7.3" "semver": "^7.6.3"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@ -3741,30 +4199,25 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-arm64": "0.33.5",
"@img/sharp-darwin-x64": "0.34.5", "@img/sharp-darwin-x64": "0.33.5",
"@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-arm64": "1.0.4",
"@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.0.4",
"@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm": "1.0.5",
"@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.0.4",
"@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.0.4",
"@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-x64": "1.0.4",
"@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
"@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-linux-arm": "0.33.5",
"@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm64": "0.33.5",
"@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-s390x": "0.33.5",
"@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-x64": "0.33.5",
"@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.33.5",
"@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.33.5",
"@img/sharp-linux-s390x": "0.34.5", "@img/sharp-wasm32": "0.33.5",
"@img/sharp-linux-x64": "0.34.5", "@img/sharp-win32-ia32": "0.33.5",
"@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-win32-x64": "0.33.5"
"@img/sharp-linuxmusl-x64": "0.34.5",
"@img/sharp-wasm32": "0.34.5",
"@img/sharp-win32-arm64": "0.34.5",
"@img/sharp-win32-ia32": "0.34.5",
"@img/sharp-win32-x64": "0.34.5"
} }
}, },
"node_modules/shebang-command": { "node_modules/shebang-command": {
@ -3872,6 +4325,16 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/simple-swizzle": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",

View File

@ -6,7 +6,11 @@
"dev": "next dev --turbopack", "dev": "next dev --turbopack",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint",
"media:inventory": "node strapi_extraction/media-sync/01-fetch-inventory.js",
"media:download": "node strapi_extraction/media-sync/02-download.js",
"media:webp": "node strapi_extraction/media-sync/03-convert-webp.js",
"media:upload": "node strapi_extraction/media-sync/04-upload-replace.js"
}, },
"dependencies": { "dependencies": {
"@strapi/blocks-react-renderer": "^1.0.1", "@strapi/blocks-react-renderer": "^1.0.1",
@ -31,6 +35,7 @@
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"postcss": "^8", "postcss": "^8",
"sharp": "^0.33.5",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5" "typescript": "^5"
} }

View File

@ -0,0 +1,139 @@
/**
* 01 Appelle lAPI Strapi (lecture publique) et produit media-inventory.json
* (liste de tous les fichiers image liés aux content-types configurés).
*
* Usage : node strapi_extraction/media-sync/01-fetch-inventory.js
*/
const fs = require("fs");
const path = require("path");
const {
API_BASE,
WORK_ROOT,
FILE_INVENTORY,
COLLECTIONS,
PAGE_SIZE,
} = require("./config");
const { normalizeField, safeSlug } = require("./lib/collect-media");
function unwrapEntry(entry) {
if (!entry) return null;
if (entry.attributes) {
return {
...entry.attributes,
id: entry.id,
documentId: entry.documentId,
};
}
return entry;
}
async function fetchJson(url) {
const r = await fetch(url);
if (!r.ok) {
const txt = await r.text();
throw new Error(`HTTP ${r.status} ${url}\n${txt.slice(0, 500)}`);
}
return r.json();
}
async function fetchAllEntries(plural) {
const out = [];
let page = 1;
/* eslint-disable no-constant-condition */
while (true) {
const params = new URLSearchParams();
params.set("pagination[page]", String(page));
params.set("pagination[pageSize]", String(PAGE_SIZE));
/**
* Strapi v5 : populate[picture]=* peut provoquer « Invalid key related » selon versions.
* populate=* hydrate les relations premier niveau (y compris les médias).
*/
params.set("populate", "*");
const url = `${API_BASE}/${plural}?${params}`;
const json = await fetchJson(url);
const rows = Array.isArray(json.data) ? json.data : [];
for (const row of rows) {
out.push(unwrapEntry(row));
}
const pageCount = json.meta?.pagination?.pageCount ?? 1;
if (page >= pageCount || rows.length === 0) break;
page += 1;
await new Promise((r) => setTimeout(r, 200));
}
return out;
}
async function main() {
console.log("🔍 STRAPI_URL (origine) →", require("./config").STRAPI_URL);
console.log("🔍 API_BASE →", API_BASE);
if (!fs.existsSync(WORK_ROOT)) {
fs.mkdirSync(WORK_ROOT, { recursive: true });
}
const inventory = {
generatedAt: new Date().toISOString(),
apiBase: API_BASE,
files: [],
};
/** @type {typeof inventory.files} */
const records = [];
for (const col of COLLECTIONS) {
console.log(`\n📂 ${col.plural} (${col.section})…`);
let entries;
try {
entries = await fetchAllEntries(col.plural);
} catch (e) {
console.error(` ⚠️ Endpoint indisponible ou erreur : ${e.message}`);
continue;
}
console.log(` ${entries.length} entrée(s)`);
for (const entry of entries) {
if (!entry) continue;
const slug = safeSlug(entry);
for (const field of col.fields) {
const items = normalizeField(entry[field.name], field.multiple);
for (const { file, index } of items) {
const base = path.basename(file.url.split("?")[0] || "file");
records.push({
collectionPlural: col.plural,
collectionSingular: col.singular,
strapiRef: col.ref,
section: col.section,
entrySlug: slug,
entryId: entry.id,
entryDocumentId: entry.documentId ?? null,
fieldName: field.name,
fieldMultiple: field.multiple,
fieldIndex: index,
fileId: file.id,
fileDocumentId: file.documentId ?? null,
filename: file.name || base,
url: file.url,
mime: file.mime,
size: file.size,
ext: file.ext,
/** chemin relatif WORK_ROOT/downloaded/... rempli par 02 */
relativeDownloadPath: null,
});
}
}
}
}
inventory.files = records.slice().sort((a, b) => a.fileId - b.fileId);
fs.writeFileSync(FILE_INVENTORY, JSON.stringify(inventory, null, 2), "utf8");
console.log(`\n✅ Inventaire : ${inventory.files.length} fichier(s) → ${FILE_INVENTORY}`);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@ -0,0 +1,108 @@
/**
* 02 Télécharge chaque fichier unique de linventaire vers
* extract/media-sync-work/downloaded/{section}/{slug}/...
* et met à jour relativeDownloadPath dans media-inventory.json
*
* Usage : node strapi_extraction/media-sync/02-download.js
*/
const fs = require("fs");
const path = require("path");
const { STRAPI_URL, FILE_INVENTORY, WORK_ROOT, DIR_DOWNLOADED } = require("./config");
function safeFilePart(name) {
return String(name || "file")
.replace(/[<>:"/\\|?*\x00-\x1f]/g, "_")
.slice(0, 180);
}
function absoluteUrl(relativeOrAbsolute) {
if (relativeOrAbsolute.startsWith("http://") || relativeOrAbsolute.startsWith("https://")) {
return relativeOrAbsolute;
}
return `${STRAPI_URL}${relativeOrAbsolute.startsWith("/") ? "" : "/"}${relativeOrAbsolute}`;
}
async function downloadBuffer(url) {
const r = await fetch(url);
if (!r.ok) {
throw new Error(`HTTP ${r.status} ${url}`);
}
const buf = Buffer.from(await r.arrayBuffer());
return buf;
}
async function main() {
if (!fs.existsSync(FILE_INVENTORY)) {
console.error("Manque linventaire. Lance dabord : 01-fetch-inventory.js");
process.exit(1);
}
const raw = fs.readFileSync(FILE_INVENTORY, "utf8");
const inventory = JSON.parse(raw);
const files = inventory.files;
if (!Array.isArray(files) || files.length === 0) {
console.log("Inventaire vide — rien à télécharger.");
return;
}
if (!fs.existsSync(DIR_DOWNLOADED)) {
fs.mkdirSync(DIR_DOWNLOADED, { recursive: true });
}
const byId = new Map();
let ok = 0;
let skipped = 0;
let fail = 0;
for (const row of files) {
const id = row.fileId;
if (byId.has(id)) {
row.relativeDownloadPath = byId.get(id);
skipped++;
continue;
}
const rel = path.join(
row.section,
row.entrySlug,
`${id}_${safeFilePart(row.filename)}`
);
const abs = path.join(DIR_DOWNLOADED, rel);
try {
const url = absoluteUrl(row.url);
const buf = await downloadBuffer(url);
fs.mkdirSync(path.dirname(abs), { recursive: true });
fs.writeFileSync(abs, buf);
row.relativeDownloadPath = rel.replace(/\\/g, "/");
byId.set(id, row.relativeDownloadPath);
ok++;
console.log(`${rel} (${(buf.length / 1024).toFixed(1)} KB)`);
} catch (e) {
console.error(`❌ fileId=${id} : ${e.message}`);
row.relativeDownloadPath = null;
row.downloadError = String(e.message);
fail++;
}
}
inventory.downloadedAt = new Date().toISOString();
inventory.stats = {
uniqueFiles: byId.size,
rowsTotal: files.length,
downloadedOk: ok,
dedupSkipped: skipped,
failed: fail,
};
fs.writeFileSync(FILE_INVENTORY, JSON.stringify(inventory, null, 2), "utf8");
console.log(`\n📁 Base téléchargements : ${DIR_DOWNLOADED}`);
console.log(
`Résumé : ${ok} téléchargement(s), ${skipped} lignes réutilisent un fichier déjà pris, ${fail} échec(s).`
);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});

View File

@ -0,0 +1,133 @@
/**
* 03 Convertit les téléchargements en WebP (sharp) sous
* extract/media-sync-work/webp/{section}/{slug}/{fileId}_{stem}.webp
* Les SVG sont copiés en .svg (sans conversion raster).
*
* Usage : node strapi_extraction/media-sync/03-convert-webp.js
*/
const fs = require("fs");
const path = require("path");
const sharp = require("sharp");
const { FILE_INVENTORY, DIR_DOWNLOADED, DIR_WEBP, WORK_ROOT } = require("./config");
const MAX_EDGE = 2560;
const WEBP_QUALITY = 82;
function stemFromFilename(filename) {
const b = path.basename(filename);
const i = b.lastIndexOf(".");
return i <= 0 ? b : b.slice(0, i);
}
async function toWebpRaster(srcPath, destPath) {
const meta = await sharp(srcPath).metadata();
const w = meta.width || 0;
const h = meta.height || 0;
let pipeline = sharp(srcPath).rotate();
if (w > MAX_EDGE || h > MAX_EDGE) {
pipeline = pipeline.resize(MAX_EDGE, MAX_EDGE, {
fit: "inside",
withoutEnlargement: true,
});
}
fs.mkdirSync(path.dirname(destPath), { recursive: true });
await pipeline.webp({ quality: WEBP_QUALITY, effort: 4 }).toFile(destPath);
}
async function main() {
if (!fs.existsSync(FILE_INVENTORY)) {
console.error("Manque media-inventory.json — lance 01 puis 02.");
process.exit(1);
}
const inventory = JSON.parse(fs.readFileSync(FILE_INVENTORY, "utf8"));
const files = inventory.files;
if (!Array.isArray(files) || !files.length) {
console.log("Rien à convertir.");
return;
}
if (!fs.existsSync(WORK_ROOT)) fs.mkdirSync(WORK_ROOT, { recursive: true });
if (!fs.existsSync(DIR_WEBP)) fs.mkdirSync(DIR_WEBP, { recursive: true });
const byId = new Map();
let ok = 0;
let err = 0;
for (const row of files) {
const id = row.fileId;
if (!row.relativeDownloadPath) {
row.relativeWebpPath = null;
continue;
}
if (byId.has(id)) {
row.relativeWebpPath = byId.get(id);
continue;
}
const src = path.join(DIR_DOWNLOADED, row.relativeDownloadPath);
if (!fs.existsSync(src)) {
row.relativeWebpPath = null;
row.convertError = "fichier téléchargé manquant";
err++;
continue;
}
const ext = path.extname(src).toLowerCase();
const baseStem = `${id}_${stemFromFilename(row.filename)}`;
const relDir = path.join(row.section, row.entrySlug);
let relOutFile;
let absOut;
try {
if (ext === ".svg") {
relOutFile = path.join(relDir, `${baseStem}.svg`).replace(/\\/g, "/");
absOut = path.join(DIR_WEBP, relOutFile);
fs.mkdirSync(path.dirname(absOut), { recursive: true });
fs.copyFileSync(src, absOut);
console.log(`svg-copy → ${relOutFile}`);
} else if (ext === ".webp") {
relOutFile = path.join(relDir, `${baseStem}.webp`).replace(/\\/g, "/");
absOut = path.join(DIR_WEBP, relOutFile);
fs.mkdirSync(path.dirname(absOut), { recursive: true });
fs.copyFileSync(src, absOut);
console.log(`webp-copy → ${relOutFile}`);
} else if ([".png", ".jpg", ".jpeg", ".tif", ".tiff"].includes(ext)) {
relOutFile = path.join(relDir, `${baseStem}.webp`).replace(/\\/g, "/");
absOut = path.join(DIR_WEBP, relOutFile);
await toWebpRaster(src, absOut);
console.log(`webp-transform → ${relOutFile}`);
} else {
relOutFile = path.join(relDir, path.basename(src)).replace(/\\/g, "/");
absOut = path.join(DIR_WEBP, relOutFile);
fs.mkdirSync(path.dirname(absOut), { recursive: true });
fs.copyFileSync(src, absOut);
console.log(`raw-copy → ${relOutFile}`);
}
row.relativeWebpPath = relOutFile;
byId.set(id, relOutFile);
ok++;
} catch (e) {
row.relativeWebpPath = null;
row.convertError = String(e.message);
err++;
console.error(`❌ fileId ${id}: ${e.message}`);
}
}
inventory.convertedAt = new Date().toISOString();
inventory.convertSettings = {
maxEdgePx: MAX_EDGE,
webpQuality: WEBP_QUALITY,
};
fs.writeFileSync(FILE_INVENTORY, JSON.stringify(inventory, null, 2), "utf8");
console.log(`\n✅ Fichiers uniques traités : ${ok}, erreurs : ${err}`);
console.log(`📁 ${DIR_WEBP}`);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});

View File

@ -0,0 +1,270 @@
/**
* 04 -upload les WebP générés vers Strapi et remplace les entrées média dans
* les fiches concernées (API authentifiée).
*
* Requiert dans lenv : STRAPI_API_TOKEN (JWT Strapi avec droits upload + mise à jour
* des collection types utilisés ; typiquement un jeton Full access en local).
*
* Par défaut : **dry-run** (aucune mutation).
* Mutation réelle : node strapi_extraction/media-sync/04-upload-replace.js --execute
*
* DANGER : sauvegarder au préalable votre base Strapi / médias. Tester sur une
* copie locale (Strapi localhost + même base si possible).
*/
const fs = require("fs");
const path = require("path");
require("dotenv").config({
path: path.join(__dirname, "../../cmsbackend/.env"),
});
require("dotenv").config({
path: path.join(__dirname, "../../.env.local"),
});
const {
FILE_INVENTORY,
DIR_WEBP,
API_BASE,
} = require("./config");
const TOKEN = process.env.STRAPI_API_TOKEN;
const EXECUTE = process.argv.includes("--execute");
function mimeForFile(filePath) {
const ext = path.extname(filePath).toLowerCase();
const map = {
".webp": "image/webp",
".svg": "image/svg+xml",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
};
return map[ext] || "application/octet-stream";
}
/** Compte combien de lignes dinventaire pointent le même fileId */
function countRefs(files) {
const c = {};
for (const r of files) {
c[r.fileId] = (c[r.fileId] || 0) + 1;
}
return c;
}
/**
* Nom réservé multipart (ByteString) : les noms avec caractères hors U+00U+FF
* (ex. U+2194 flèches dans des noms générés) provoquent lerreur Node
* « Cannot convert argument to a ByteString » sur form.append(..., filename).
*/
function asciiUploadName(filePath, fileId) {
const ext = path.extname(filePath).toLowerCase();
const extOk = /^\.(webp|svg|png|jpe?g|gif|avif)$/.test(ext) ? ext : ".webp";
return `upload-${fileId}${extOk}`;
}
async function postUpload(filePath, fileId) {
const buf = fs.readFileSync(filePath);
const asciiName = asciiUploadName(filePath, fileId);
const blob = new Blob([buf], { type: mimeForFile(filePath) });
const body = new FormData();
body.append("files", blob, asciiName);
const res = await fetch(`${API_BASE}/upload`, {
method: "POST",
headers: TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {},
body,
});
if (!res.ok) {
const t = await res.text();
throw new Error(`POST /upload ${res.status} ${t.slice(0, 800)}`);
}
const json = await res.json();
const arr = Array.isArray(json) ? json : json?.data;
if (!Array.isArray(arr) || !arr[0]) {
throw new Error(`Réponse upload inattendue : ${JSON.stringify(json).slice(0, 400)}`);
}
return arr[0];
}
function unwrapEntry(raw) {
if (!raw) return null;
if (raw.data) return unwrapEntry(raw.data);
if (raw.attributes) {
return {
...raw.attributes,
id: raw.id,
documentId: raw.documentId,
};
}
return raw;
}
async function getEntryPlural(plural, documentId) {
const qs = new URLSearchParams({ populate: "*" });
const url = `${API_BASE}/${plural}/${documentId}?${qs}`;
const res = await fetch(url, {
headers: TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {},
});
if (!res.ok) {
const t = await res.text();
throw new Error(`GET ${url}${res.status} ${t.slice(0, 400)}`);
}
const json = await res.json();
return unwrapEntry(json);
}
function getMediaIdsFromField(entry, fieldName, multiple) {
const v = entry[fieldName];
if (multiple) {
const arr = Array.isArray(v) ? v : [];
return arr.map((x) => (typeof x === "object" && x !== null ? x.id : x)).filter(Boolean);
}
if (!v) return [];
if (typeof v === "object" && v.id) return [v.id];
if (typeof v === "number") return [v];
return [];
}
async function putEntry(plural, documentId, payloadData) {
const url = `${API_BASE}/${plural}/${documentId}`;
const res = await fetch(url, {
method: "PUT",
headers: {
"Content-Type": "application/json",
...(TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {}),
},
body: JSON.stringify({ data: payloadData }),
});
if (!res.ok) {
const t = await res.text();
throw new Error(`PUT ${url}${res.status} ${t.slice(0, 800)}`);
}
return res.json();
}
async function deleteFile(fileId) {
const url = `${API_BASE}/upload/files/${fileId}`;
const res = await fetch(url, {
method: "DELETE",
headers: TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {},
});
if (!res.ok) {
const t = await res.text();
throw new Error(`DELETE ${url}${res.status} ${t.slice(0, 400)}`);
}
}
async function main() {
if (!fs.existsSync(FILE_INVENTORY)) {
console.error("Manque media-inventory.json");
process.exit(1);
}
if (!TOKEN) {
console.error(
"STRAPI_API_TOKEN manquant. Crée un jeton API dans Strapi (Settings → API Tokens), puis exporte :\n" +
" Windows PowerShell : $env:STRAPI_API_TOKEN=\"…\"\n" +
" ou ajoute STRAPI_API_TOKEN dans cmsbackend/.env (non commité)."
);
process.exit(1);
}
const inventory = JSON.parse(fs.readFileSync(FILE_INVENTORY, "utf8"));
const files = inventory.files || [];
const refCount = countRefs(files);
const candidates = files.filter(
(r) =>
r.relativeWebpPath &&
r.relativeWebpPath.endsWith(".webp") &&
r.entryDocumentId
);
console.log(
`Lignes inventaire : ${files.length} — candidats WebP remplaçables (avec documentId) : ${candidates.length}`
);
console.log(`Mode : ${EXECUTE ? "EXECUTE (mutations réelles)" : "DRY-RUN (aucune mutation)"}\n`);
if (!EXECUTE) {
console.log(
"Exemple dactions qui seraient effectuées avec --execute :\n" +
" 1. POST /api/upload pour chaque fichier .webp sous webp/\n" +
" 2. PUT /api/:collection/:documentId avec le champ média mis à jour (ids)\n" +
" 3. DELETE /api/upload/files/:oldFileId uniquement si lancien id na quune référence dans linventaire\n"
);
for (let i = 0; i < Math.min(5, candidates.length); i++) {
const r = candidates[i];
console.log(
` • fileId=${r.fileId}${r.relativeWebpPath} → entrée ${r.collectionPlural} documentId=${r.entryDocumentId} champ=${r.fieldName}[${r.fieldIndex}]`
);
}
console.log("\nAjoute --execute pour réellement téléverser (après avoir testé la sauvegarde).");
return;
}
/** Par sécurité, traite fichier par fichier ; re-fetch après chaque succès évite désalignement indices */
for (const row of candidates) {
const src = path.join(DIR_WEBP, row.relativeWebpPath.replace(/\//g, path.sep));
if (!fs.existsSync(src)) {
console.warn(`SKIP fileId=${row.fileId} : fichier absent ${src}`);
continue;
}
const docId = row.entryDocumentId;
const plural = row.collectionPlural;
const field = row.fieldName;
const idx = row.fieldIndex;
const oldId = row.fileId;
try {
const uploaded = await postUpload(src, row.fileId);
const newId = uploaded.id;
const entry = await getEntryPlural(plural, docId);
if (!entry) throw new Error("entrée introuvable");
const multiple = row.fieldMultiple;
const currentIds = getMediaIdsFromField(entry, field, multiple);
if (multiple) {
if (idx < 0 || idx >= currentIds.length) {
throw new Error(`index ${idx} hors limites (ids actuels : ${currentIds.join(",")})`);
}
if (currentIds[idx] !== oldId) {
throw new Error(
`id à lindex ${idx} est ${currentIds[idx]}, attendu ${oldId} — arrêt pour éviter corruption`
);
}
const next = currentIds.slice();
next[idx] = newId;
await putEntry(plural, docId, { [field]: next });
} else {
const cur = currentIds[0];
if (cur != null && cur !== oldId) {
throw new Error(`champ simple : attendu ancien id ${oldId}, vu ${cur}`);
}
await putEntry(plural, docId, { [field]: newId });
}
console.log(`OK upload+remplace : ${oldId}${newId} (${plural}/${docId})`);
const canDeleteOld = refCount[oldId] === 1;
if (canDeleteOld && oldId !== newId) {
try {
await deleteFile(oldId);
console.log(` ancien fichier Strapi ${oldId} supprimé`);
} catch (de) {
console.warn(` suppression ancien échouée (non bloquant) : ${de.message}`);
}
}
} catch (e) {
console.error(`ÉCHEC fileId=${row.fileId} : ${e.message}`);
}
}
console.log("\nTerminé. Recharge les fiches dans ladmin et vide le cache navigateur.");
}
main().catch((e) => {
console.error(e);
process.exit(1);
});

View File

@ -0,0 +1,58 @@
# Pipeline médias Strapi → dossiers par section → WebP → ré-upload optionnel
**Répertoire de travail (lourd)** : `strapi_extraction/extract/media-sync-work/` — ignoré par Git (`/.gitignore`).
## Prérequis
- Node 18+
- Dépendance `sharp` installée depuis la racine (`npm install` déjà fait si le dépôt à jour).
## Variables optionnelles
| Variable | Rôle |
|----------|------|
| `STRAPI_URL` | Origine Strapi sans `/api` (défaut depuis `NEXT_PUBLIC_API_URL` ou prod). |
| `STRAPI_API_TOKEN` | **Seulement** pour `04-upload-replace.js --execute`. Jeton créé dans Strapi → Settings → API Tokens (droits lecture + Upload + mise à jour des CT concernés). Ne pas committer ce jeton. |
Chemins `.env.local` à la racine et `cmsbackend/.env` sont chargés par les scripts (`config.js` ou `04`).
## Chaîne normale
```powershell
# depuis la racine du repo J:\my-next-site
npm run media:inventory # 01 — liste tous les médias utilisés → media-inventory.json
npm run media:download # 02 — téléchargement physique par section sous downloaded/
npm run media:webp # 03 — conversion/copies sous webp/
npm run media:upload # 04 — dry-run uniquement (aucune mutation)
# Mutation CMS (**attention**) après lecture cidessous :
$env:STRAPI_API_TOKEN="<jeton>"
node strapi_extraction/media-sync/04-upload-replace.js --execute
```
### Organisation des dossiers
- **`downloaded/{section}/{slug-du-contenu}/{fileId}_{nom-fichier}`**
Sections : `portfolio`, `competences`, `home`, `realisation-ias`, `glossaire`.
- **`webp/...`** même structure, avec `.webp` (ou `.svg` copié en clair).
Les **fichiers média uniques** sont dédupliqués par `fileId` : une seule fois sur disque, plusieurs lignes dans linventaire peuvent référencer le même téléchargement.
### Erreur « Cannot convert argument to a ByteString » (upload)
Les noms de fichiers sur disque peuvent contenir des caractères Unicode (tirets fins, flèches dans des noms AI, etc.). Le `FormData` HTTP naccepte quun nom de fichier **ASCII** dans len-tête multipart ; le script **`04-upload-replace.js`** renomme donc en interne chaque envoi en `upload-{fileId}.webp` (voir `asciiUploadName` dans le fichier). Relance `--execute` après mise à jour du script.
### Avant le `--execute` sur la prod
1. **Tester dabord** contre une instance Strapi locale (ex. importer la base vers `cmsbackend`, `npm run develop`, puis `$env:STRAPI_URL="http://localhost:1337"` et un jeton local).
2. **Sauvegarder** la base et `/public/uploads`.
3. Lire le préambule dans `04-upload-replace.js` ; le script vérifie que lancien id correspond encore avant remplacement pour limiter les corruptions.
## Si létape ré-upload vous suffit après conversion manuelle
Vous pouvez aussi **importer uniquement les WebP** depuis ladmin Strapi puis réassigner les champs à la main ; ce dépôt ne vous oblige pas à utiliser `04`.

View File

@ -0,0 +1,78 @@
/**
* Configuration partagée sync médias Strapi (téléchargement / WebP / -upload).
* Variables d'environnement (optionnelles) :
* STRAPI_URL origine sans /api (ex. https://api.fernandgrascalvet.com ou http://localhost:1337)
* STRAPI_API_TOKEN jeton API Strapi (requis pour 04-upload-replace.js --execute)
*/
require("dotenv").config({ path: require("path").join(__dirname, "../../.env.local") });
require("dotenv").config({ path: require("path").join(__dirname, "../../cmsbackend/.env") });
const path = require("path");
const STRAPI_URL =
process.env.STRAPI_URL ||
process.env.NEXT_PUBLIC_API_URL?.replace(/\/$/, "") ||
"https://api.fernandgrascalvet.com";
const API_BASE = `${STRAPI_URL}/api`;
/** Sortie : mirrors extract/ existant (gitignored lourd) */
const WORK_ROOT = path.join(__dirname, "../extract/media-sync-work");
const DIR_DOWNLOADED = path.join(WORK_ROOT, "downloaded");
const DIR_WEBP = path.join(WORK_ROOT, "webp");
const FILE_INVENTORY = path.join(WORK_ROOT, "media-inventory.json");
/**
* Content-types avec champs média aligné sur cmsbackend/src/api (schema.json par type).
* section = sous-dossier humain (portfolio, competences, )
*/
const COLLECTIONS = [
{
plural: "projects",
singular: "project",
ref: "api::project.project",
section: "portfolio",
fields: [{ name: "picture", multiple: true }],
},
{
plural: "competences",
singular: "competence",
ref: "api::competence.competence",
section: "competences",
fields: [{ name: "picture", multiple: true }],
},
{
plural: "homepages",
singular: "homepage",
ref: "api::homepage.homepage",
section: "home",
fields: [{ name: "photo", multiple: false }],
},
{
plural: "realisation-ias",
singular: "realisation-ia",
ref: "api::realisation-ia.realisation-ia",
section: "realisation-ias",
fields: [{ name: "picture", multiple: true }],
},
{
plural: "glossaires",
singular: "glossaire",
ref: "api::glossaire.glossaire",
section: "glossaire",
fields: [{ name: "images", multiple: true }],
},
];
const PAGE_SIZE = 100;
module.exports = {
STRAPI_URL,
API_BASE,
WORK_ROOT,
DIR_DOWNLOADED,
DIR_WEBP,
FILE_INVENTORY,
COLLECTIONS,
PAGE_SIZE,
};

View File

@ -0,0 +1,41 @@
/**
* Helpers extraire les fichiers média des réponses Strapi v5 (structure plate).
*/
function isUploadedImage(obj) {
if (!obj || typeof obj !== "object" || typeof obj.url !== "string") return false;
if (!obj.url.includes("/uploads/")) return false;
if (obj.mime && !obj.mime.startsWith("image/")) return false;
return true;
}
/** Liste { file, index } pour un champ média Strapi */
function normalizeField(fieldVal, multiple) {
if (!fieldVal) return [];
if (multiple) {
const arr = Array.isArray(fieldVal) ? fieldVal : [fieldVal];
return arr
.filter(isUploadedImage)
.map((file, index) => ({ file, index }));
}
return isUploadedImage(fieldVal)
? [{ file: fieldVal, index: 0 }]
: [];
}
function safeSlug(entry) {
const s =
entry.slug ??
entry.documentId ??
(entry.id != null ? String(entry.id) : "unknown");
return String(s)
.replace(/[^\wÀ-ÖØ-öø-ÿ.-]+/gu, "_")
.slice(0, 96)
.replace(/^_|_$/g, "") || "entry";
}
module.exports = {
normalizeField,
safeSlug,
isUploadedImage,
};