├── .devcontainer ├── Dockerfile ├── devcontainer.json ├── docker-compose.yml ├── imgproxy.env ├── init.sh ├── minio-dump │ └── test-bucket │ │ ├── test-image.png │ │ └── test-image.svg ├── minio.env └── workspace.env ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ ├── dependabot-auto-merge.yml │ └── publish-npm-package.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── example ├── .gitignore ├── README.md ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages │ ├── _app.tsx │ ├── api │ │ └── image.ts │ └── index.tsx ├── public │ ├── favicon.ico │ └── vercel.svg ├── server.js ├── styles │ └── global.css └── tsconfig.json ├── package-lock.json ├── package.json ├── request_flow.png ├── rollup.config.mjs ├── scripts ├── minio-dump.sh └── minio-restore.sh ├── src ├── index.tsx └── logger.ts ├── tsconfig.build.json └── tsconfig.json /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # Update the VARIANT arg in docker-compose.yml to pick a Node version: 10, 12, 14 2 | ARG VARIANT=20 3 | FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} 4 | 5 | # See https://github.com/microsoft/vscode-dev-containers/tree/master/containers/docker-from-docker for more documentation 6 | # On how to use docker from within docker 7 | 8 | # Install Docker CE CLI 9 | RUN apt-get update \ 10 | && apt-get install -y apt-transport-https ca-certificates curl gnupg2 lsb-release \ 11 | && curl -fsSL https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/gpg | apt-key add - 2>/dev/null \ 12 | && echo "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]') $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list \ 13 | && apt-get update \ 14 | && apt-get install -y docker-ce-cli 15 | 16 | # Install Docker Compose 17 | RUN LATEST_COMPOSE_VERSION=$(curl -sSL "https://api.github.com/repos/docker/compose/releases/latest" | grep -o -P '(?<="tag_name": ").+(?=")') \ 18 | && curl -sSL "https://github.com/docker/compose/releases/download/${LATEST_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose \ 19 | && chmod +x /usr/local/bin/docker-compose 20 | 21 | # Update args in docker-compose.yaml to set the UID/GID of the "node" user. 22 | ARG USER_UID=1000 23 | ARG USER_GID=$USER_UID 24 | RUN if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then groupmod --gid $USER_GID node && usermod --uid $USER_UID --gid $USER_GID node; fi 25 | 26 | # Add the node user to the docker group to access 27 | # the daemon without sudo 28 | RUN groupadd docker 29 | RUN usermod -a -G docker node 30 | 31 | # Add minio CLI 32 | # See https://min.io/download#/linux 33 | RUN wget https://dl.min.io/client/mc/release/linux-$(dpkg --print-architecture)/mc \ 34 | && mv mc /usr/local/bin/minio-cli \ 35 | && chmod +x /usr/local/bin/minio-cli 36 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-image-s3-imgproxy-loader", 3 | "dockerComposeFile": "docker-compose.yml", 4 | "service": "application", 5 | "workspaceFolder": "/workspace", 6 | "customizations": { 7 | "vscode": { 8 | "settings": { 9 | "terminal.integrated.defaultProfile.linux": "bash" 10 | }, 11 | "remoteEnv": { 12 | "PATH": "${containerEnv:PATH}:/workspace/bin" 13 | }, 14 | "extensions": [ 15 | "dbaeumer.vscode-eslint", 16 | "esbenp.prettier-vscode", 17 | "mikestead.dotenv", 18 | "ms-azuretools.vscode-docker", 19 | "ms-vsliveshare.vsliveshare", 20 | "wayou.vscode-todo-highlight", 21 | "xshrim.txt-syntax" 22 | ] 23 | } 24 | }, 25 | "forwardPorts": [3000, 4000, 9000, 9001], 26 | "postCreateCommand": "bash -i .devcontainer/init.sh", 27 | "remoteUser": "node", 28 | "portsAttributes": { 29 | "3000": { 30 | "label": "Example Application", 31 | "onAutoForward": "notify" 32 | }, 33 | "4000": { 34 | "label": "Imgproxy Server" 35 | }, 36 | "9000": { 37 | "label": "Minio Server" 38 | }, 39 | "9001": { 40 | "label": "Minio Console" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | networks: 4 | next-image-s3-imgproxy-loader-network: 5 | name: next-image-s3-imgproxy-loader-network 6 | driver: bridge 7 | 8 | volumes: 9 | next-image-s3-imgproxy-loader-minio-latest__data: 10 | driver: local 11 | 12 | services: 13 | application: 14 | container_name: next-image-s3-imgproxy-loader 15 | build: 16 | context: . 17 | dockerfile: Dockerfile 18 | args: 19 | VARIANT: 20 20 | USER_UID: 1000 21 | USER_GID: 1000 22 | networks: 23 | - next-image-s3-imgproxy-loader-network 24 | volumes: 25 | - ..:/workspace:cached 26 | - ~/.ssh:/home/node/.ssh:ro 27 | - /var/run/docker.sock:/var/run/docker.sock 28 | command: sleep infinity 29 | user: node 30 | env_file: 31 | - ./workspace.env 32 | ports: 33 | - 4000:4000 34 | - 9000:9000 35 | - 9001:9001 36 | 37 | # Minio 38 | next-image-s3-imgproxy-loader-minio-latest: 39 | container_name: next-image-s3-imgproxy-loader-minio-latest 40 | image: minio/minio:latest 41 | network_mode: service:application 42 | volumes: 43 | - type: volume 44 | source: next-image-s3-imgproxy-loader-minio-latest__data 45 | target: /data 46 | volume: 47 | nocopy: false 48 | command: server /data --console-address :9001 49 | env_file: 50 | - ./minio.env 51 | 52 | # Imgproxy 53 | next-image-s3-imgproxy-loader-imgproxy-latest: 54 | container_name: next-image-s3-imgproxy-loader-imgproxy-latest 55 | image: darthsim/imgproxy:latest 56 | network_mode: service:application 57 | env_file: 58 | - ./imgproxy.env 59 | -------------------------------------------------------------------------------- /.devcontainer/imgproxy.env: -------------------------------------------------------------------------------- 1 | IMGPROXY_BIND=0.0.0.0:4000 2 | IMGPROXY_ALLOWED_SOURCES=s3:// 3 | IMGPROXY_USE_S3=true 4 | IMGPROXY_S3_ENDPOINT=http://localhost:9000 5 | IMGPROXY_KEY=91bdcda48ce22cd7d8d3a0eda930b3db1762bc1cba5dc13542e723b68fe55d6f9d18199cbe35191a45faf22593405cad0fe76ffec67d24f8aee861ac8fe44d96 6 | IMGPROXY_SALT=72456c286761260f320391fe500fcec53755958dabd288867a6db072e1bc1dbd84b15079838a83a715edc1ecad50c3ce91dd8fdef6f981816fa274f91d8ecf06 7 | AWS_ACCESS_KEY_ID=minio_user 8 | AWS_SECRET_ACCESS_KEY=minio_password -------------------------------------------------------------------------------- /.devcontainer/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Init minio 4 | minio-cli alias set myminio/ http://localhost:9000 minio_user minio_password 5 | 6 | # Init project 7 | nvm install 8 | nvm use 9 | nvm alias default $(node --version) 10 | nvm install-latest-npm 11 | npm i 12 | -------------------------------------------------------------------------------- /.devcontainer/minio-dump/test-bucket/test-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitPatty/next-image-s3-imgproxy-loader/a4787993d28d3fafd883a36fbe7242160945229c/.devcontainer/minio-dump/test-bucket/test-image.png -------------------------------------------------------------------------------- /.devcontainer/minio-dump/test-bucket/test-image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 67 | 72 | 78 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /.devcontainer/minio.env: -------------------------------------------------------------------------------- 1 | MINIO_ACCESS_KEY=minio_user 2 | MINIO_SECRET_KEY=minio_password -------------------------------------------------------------------------------- /.devcontainer/workspace.env: -------------------------------------------------------------------------------- 1 | IMGPROXY_URL=http://localhost:4000 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.css text eol=lf 2 | *.env text eol=lf 3 | *.js text eol=lf 4 | *.json text eol=lf 5 | *.lock text eol=lf 6 | *.md text eol=lf 7 | *.sh text eol=lf 8 | *.svg text eol=lf 9 | *.ts text eol=lf 10 | *.tsx text eol=lf 11 | *.yml text eol=lf 12 | 13 | # Dockerfiles 14 | *.Dockerfile text eol=lf 15 | Dockerfile text eol=lf 16 | 17 | # dotfiles 18 | .git* text eol=lf 19 | .npmignore text eol=lf 20 | .nvmrc text eol=lf 21 | .prettierrc text eol=lf 22 | 23 | # Binaries 24 | *.ico binary 25 | *.jpg binary 26 | *.png binary 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: '/' 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 5 8 | reviewers: 9 | - bitpatty 10 | labels: 11 | - dependencies 12 | ignore: 13 | - dependency-name: '@types/node' 14 | groups: 15 | production-dependencies: 16 | dependency-type: 'production' 17 | development-dependencies: 18 | dependency-type: 'development' 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: push 4 | 5 | jobs: 6 | build: 7 | if: github.repository_owner == 'bitpatty' 8 | name: Build Project 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: '22.10.0' 15 | - run: npm install 16 | - run: npm run build 17 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: auto-merge 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | auto-merge: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: ahmadnassri/action-dependabot-auto-merge@v2 12 | with: 13 | target: minor 14 | github-token: ${{ secrets.BOT_TOKEN }} 15 | -------------------------------------------------------------------------------- /.github/workflows/publish-npm-package.yml: -------------------------------------------------------------------------------- 1 | name: Publish npm package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | publish-github: 9 | if: github.repository_owner == 'bitpatty' 10 | name: Publish NPM package (GitHub Registry) 11 | runs-on: ubuntu-latest 12 | environment: github-registry 13 | permissions: 14 | contents: read 15 | packages: write 16 | steps: 17 | - uses: actions/checkout@v3 18 | - uses: actions/setup-node@v3 19 | with: 20 | node-version: '22.10.0' 21 | registry-url: 'https://npm.pkg.github.com' 22 | scope: '@bitpatty' 23 | - run: npm install 24 | - run: npm run build 25 | - run: echo "registry=https://npm.pkg.github.com/@bitpatty" >> .npmrc 26 | - run: npm publish 27 | env: 28 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | publish-npmjs: 31 | if: github.repository_owner == 'bitpatty' 32 | name: Publish NPM package (npmjs.org) 33 | runs-on: ubuntu-latest 34 | environment: npm-registry 35 | steps: 36 | - uses: actions/checkout@v3 37 | - uses: actions/setup-node@v3 38 | with: 39 | node-version: '22.10.0' 40 | registry-url: 'https://registry.npmjs.org' 41 | - run: npm install 42 | - run: npm run build 43 | - run: npm publish --access=public 44 | env: 45 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | .nvmrc 3 | .prettierrc 4 | .vscode 5 | example 6 | package-lock.json 7 | rollup.config.js 8 | scripts 9 | src 10 | tsconfig.json -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v22.10.0 -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 80 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[html]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | }, 5 | "[javascript]": { 6 | "editor.defaultFormatter": "esbenp.prettier-vscode" 7 | }, 8 | "[javascriptreact]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode" 13 | }, 14 | "[typescriptreact]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | }, 17 | "[json]": { 18 | "editor.quickSuggestions": { 19 | "strings": true 20 | }, 21 | "editor.suggest.insertMode": "replace", 22 | "editor.defaultFormatter": "esbenp.prettier-vscode" 23 | }, 24 | "files.autoSave": "off", 25 | "breadcrumbs.symbolSortOrder": "type", 26 | "coverage-gutters.coverageReportFileName": "coverage/**/index.html", 27 | "coverage-gutters.showLineCoverage": true, 28 | "editor.codeLens": true, 29 | "editor.detectIndentation": true, 30 | "editor.formatOnSave": true, 31 | "editor.minimap.maxColumn": 150, 32 | "editor.tabSize": 2, 33 | "explorer.confirmDragAndDrop": false, 34 | "files.associations": { 35 | "*.erb": "html", 36 | "*.html.erb": "html" 37 | }, 38 | "git.confirmSync": false, 39 | "git.enableSmartCommit": true, 40 | "html.format.wrapLineLength": 150, 41 | "javascript.updateImportsOnFileMove.enabled": "always", 42 | "search.exclude": { 43 | "**/*.eot": true, 44 | "**/*.png": true, 45 | "**/*.svg": true, 46 | "**/*.ttf": true, 47 | "**/*.woff": true, 48 | "**/*.woff2": true, 49 | "**/.git": true, 50 | "**/bower_components": true, 51 | "**/dist/": true, 52 | "**/node_modules": true, 53 | "**/tmp": true 54 | }, 55 | "todohighlight.keywords": [ 56 | "@TODO" 57 | ], 58 | "typescript.preferences.importModuleSpecifier": "relative", 59 | "typescript.referencesCodeLens.enabled": true, 60 | "typescript.referencesCodeLens.showOnAllFunctions": true, 61 | "typescript.reportStyleChecksAsWarnings": true, 62 | "typescript.updateImportsOnFileMove.enabled": "always", 63 | "workbench.editor.enablePreview": false, 64 | "workbench.editor.enablePreviewFromQuickOpen": false 65 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Matteias Collet and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `next/image` loader for imgproxy (S3) 2 | 3 | This library is a layer on top of the the [next/image](https://nextjs.org/docs/api-reference/next/image) component, which allows you to load images from an [imgproxy](https://github.com/imgproxy/imgproxy) instance connected to an S3. With this library, the NextJS server acts as a middleman between the client and the imgproxy instance to perform additional tasks, such as applying signatures and/or guards before loading an image. 4 | 5 | > **If you want to access the imgproxy instance directly from your client you can simply use the `next/image` component itself - no need to install this library** (you might wanna look into the [imgproxy-url-builder](https://github.com/BitPatty/imgproxy-url-builder) to build the request URL however). 6 | 7 | ![Request Flow](https://github.com/BitPatty/next-image-s3-imgproxy-loader/raw/master/request_flow.png) 8 | 9 | ## Sample Usage 10 | 11 | > You can find additional examples in the [demo project](https://github.com/BitPatty/next-image-s3-imgproxy-loader/blob/master/example/pages/index.tsx). 12 | 13 | ### Installation 14 | 15 | You can install the package via npm: 16 | 17 | ```sh 18 | npm install --save @bitpatty/next-image-s3-imgproxy-loader 19 | ``` 20 | 21 | ### Registering the endpoint 22 | 23 | #### Method 1: Custom Server 24 | 25 | The library by default proxies request through a custom endpoint. To register the endpoint create a [custom server](https://nextjs.org/docs/advanced-features/custom-server) in your project and add the following lines: 26 | 27 | ```js 28 | // server.js 29 | const imgProxy = require('@bitpatty/next-image-s3-imgproxy-loader'); 30 | 31 | app.prepare().then(() => { 32 | createServer((req, res) => { 33 | const parsedUrl = parse(req.url, true); 34 | const { pathname, query } = parsedUrl; 35 | 36 | if (pathname === imgProxy.IMGPROXY_ENDPOINT) { 37 | // Add other middleware here, such as auth guards 38 | // ... 39 | imgProxy.handle( 40 | new URL(''), 41 | query, 42 | res, 43 | // (Optional) Additional configuration options 44 | { 45 | // (Optional) If your imgproxy uses signatures, specify 46 | // the key and salt here 47 | signature: { 48 | // (Required) The IMGPROXY_KEY (hex encoded) 49 | key: '', 50 | // (Required) The IMGPROXY_SALT (hex encoded) 51 | salt: '', 52 | }, 53 | // (Optional) If your imgproxy instance uses 54 | // the IMGPROXY_SECRET, specify the token here 55 | authToken: '', 56 | // (Optional) If you wanna restrict access to specific 57 | // buckets add an array of valid bucket names 58 | bucketWhitelist: [''], 59 | // (Optional) A list of imgproxy headers that should be 60 | // forwarded through the imgproxy endpoint 61 | forwardedHeaders: [''], 62 | // (Optional) An object containing additional request 63 | // headers that should be sent to the imgproxy endpoint 64 | requestHeaders: { 65 | 'My-Header': 'My-Value', 66 | // ... 67 | }, 68 | // (Optional) The logger configuration. If you want additional 69 | // debug output you can adjust the log level. 70 | logging: { 71 | // (Optional) The logger to use (defaults to console) 72 | // The logger should implement the signature for 73 | // for console.debug, console.warn and console.error 74 | logger: console, 75 | // (Optional) The log level, must be one of 76 | // 'debug', 'warn' or 'error' (defaults to 'error') 77 | level: 'debug', 78 | }, 79 | }, 80 | ); 81 | } else { 82 | handle(req, res, parsedUrl); 83 | } 84 | }).listen(3000, (err) => { 85 | if (err) throw err; 86 | console.log('> Ready on http://localhost:3000'); 87 | }); 88 | }); 89 | ``` 90 | 91 | #### Method 2: API Endpoint 92 | 93 | For serverless environments you can use an API endpoint instead of a custom endpoint. 94 | 95 | The setup is similar, register your endpoint as follows: 96 | 97 | ```typescript 98 | // pages/api/image.ts 99 | import { NextApiRequest, NextApiResponse } from 'next'; 100 | import { handle } from '@bitpatty/next-image-s3-imgproxy-loader'; 101 | 102 | const handler = (req: NextApiRequest, res: NextApiResponse): void => { 103 | if (req.method !== 'GET') { 104 | res.statusCode = 405; 105 | res.send(''); 106 | return; 107 | } 108 | 109 | handle(new URL('http://localhost:4000/'), req.query, res, { 110 | // Handler Options 111 | }); 112 | }; 113 | 114 | export default handler; 115 | ``` 116 | 117 | With this method, you have to [supply the endpoint](#overriding-the-endpoint) to the `` component. 118 | 119 | ## Using the component 120 | 121 | After registering the endpoint you can use the `` component as you would with the `Image` component from `next/image`, except that you need to provide a file (`/`) instead of `src` and optional proxy params for image transformations/optimizations. 122 | 123 | ```tsx 124 | import ProxyImage from '@bitpatty/next-image-s3-imgproxy-loader'; 125 | import pb from '@bitpatty/imgproxy-url-builder'; 126 | 127 | ; 131 | ``` 132 | 133 | > Note: The layout prop is automatically set to 'fill' if no width is set 134 | 135 | ## Using the raw path 136 | 137 | In case using the component is not an option, you can instead use the image path itself, by utilizing the `buildProxyImagePath` function. 138 | 139 | ```tsx 140 | import { buildProxyImagePath } from '@bitpatty/next-image-s3-imgproxy-loader'; 141 | import pb from '@bitpatty/imgproxy-url-builder'; 142 | 143 | const imagePath = buildProxyImagePath('test-bucket/test-image.png', { 144 | proxyParams: pb().blur(10).build(), 145 | }); 146 | 147 | ; 148 | ``` 149 | 150 | or as background image 151 | 152 | ```tsx 153 | import { buildProxyImagePath } from '@bitpatty/next-image-s3-imgproxy-loader'; 154 | import pb from '@bitpatty/imgproxy-url-builder'; 155 | 156 | const imagePath = buildProxyImagePath('test-bucket/test-image.png', { 157 | proxyParams: pb().blur(10).format('jpg').build(), 158 | }); 159 | 160 |
166 | {/* Content */} 167 |
; 168 | ``` 169 | 170 | ## Overriding the endpoint 171 | 172 | If you use a different endpoint than the one provided by `IMGPROXY_ENDPOINT` you can override the endpoint used by the component by providing the `endpoint` property. `endpoint` can be both a path but also a URL. 173 | 174 | ```tsx 175 | ; 176 | 177 | // Or 178 | buildProxyImagePath('test-bucket/test-image.png', { 179 | endpoint: '/my-endpoint', 180 | }); 181 | ``` 182 | 183 | ## Forwarded Headers 184 | 185 | By default, the following imgproxy headers will be forwarded in the response to the client: 186 | 187 | ```json 188 | [ 189 | "date", 190 | "expires", 191 | "content-type", 192 | "content-length", 193 | "cache-control", 194 | "content-disposition", 195 | "content-dpr" 196 | ] 197 | ``` 198 | 199 | If you want to forward a different set of headers you can use the `forwardedHeaders` option to specify a custom list of headers. Note that if `forwardedHeaders` is specified, all headers not specified in the list will be omitted in the response. 200 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /example/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /example/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | reactStrictMode: true, 4 | }; 5 | -------------------------------------------------------------------------------- /example/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "example", 9 | "version": "0.1.0", 10 | "dependencies": { 11 | "@bitpatty/next-image-s3-imgproxy-loader": "file:.." 12 | } 13 | }, 14 | "..": { 15 | "name": "@bitpatty/next-image-s3-imgproxy-loader", 16 | "version": "0.13.0", 17 | "license": "MIT", 18 | "dependencies": { 19 | "@bitpatty/imgproxy-url-builder": "^1.3.4" 20 | }, 21 | "devDependencies": { 22 | "@types/node": "22.8.1", 23 | "@types/react": "18.3.12", 24 | "next": "15.0.1", 25 | "npm-check-updates": "17.1.6", 26 | "prettier": "3.3.3", 27 | "rollup": "4.24.0", 28 | "rollup-plugin-typescript2": "0.36.0", 29 | "tslib": "2.8.0", 30 | "typescript": "5.6.3" 31 | }, 32 | "peerDependencies": { 33 | "next": ">=13.0.0 <16.0.0" 34 | } 35 | }, 36 | "node_modules/@bitpatty/next-image-s3-imgproxy-loader": { 37 | "resolved": "..", 38 | "link": true 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "node server.js", 7 | "build": "next build", 8 | "start": "NODE_ENV=production node server.js" 9 | }, 10 | "dependencies": { 11 | "@bitpatty/next-image-s3-imgproxy-loader": "file:.." 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/global.css'; 2 | 3 | import type { AppProps } from 'next/app'; 4 | 5 | function MyApp({ Component, pageProps }: AppProps) { 6 | return ; 7 | } 8 | export default MyApp; 9 | -------------------------------------------------------------------------------- /example/pages/api/image.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from 'next'; 2 | import { handle } from '@bitpatty/next-image-s3-imgproxy-loader'; 3 | 4 | const handler = (req: NextApiRequest, res: NextApiResponse): void => { 5 | if (req.method !== 'GET') { 6 | res.statusCode = 405; 7 | res.send(''); 8 | return; 9 | } 10 | 11 | handle(new URL('http://localhost:4000/'), req.query, res, { 12 | signature: { 13 | key: '91bdcda48ce22cd7d8d3a0eda930b3db1762bc1cba5dc13542e723b68fe55d6f9d18199cbe35191a45faf22593405cad0fe76ffec67d24f8aee861ac8fe44d96', 14 | salt: '72456c286761260f320391fe500fcec53755958dabd288867a6db072e1bc1dbd84b15079838a83a715edc1ecad50c3ce91dd8fdef6f981816fa274f91d8ecf06', 15 | }, 16 | bucketWhitelist: ['test-bucket'], 17 | }); 18 | }; 19 | 20 | export default handler; 21 | -------------------------------------------------------------------------------- /example/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import pb, { GravityType, ResizeType } from '@bitpatty/imgproxy-url-builder'; 2 | import type { NextPage } from 'next'; 3 | import Head from 'next/head'; 4 | 5 | import { 6 | ProxyImage, 7 | buildProxyImagePath, 8 | } from '@bitpatty/next-image-s3-imgproxy-loader'; 9 | import { useEffect, useRef, useState } from 'react'; 10 | 11 | const demoContent: { 12 | label: string; 13 | file: string; 14 | proxyParams?: string; 15 | layout?: 'fill'; 16 | width?: number; 17 | height?: number; 18 | }[] = [ 19 | { 20 | label: 'Original Image', 21 | file: 'test-bucket/test-image.png', 22 | }, 23 | { 24 | label: 'Changing filetype', 25 | file: 'test-bucket/test-image.png', 26 | }, 27 | { 28 | label: 'Blurring', 29 | file: 'test-bucket/test-image.png', 30 | 31 | proxyParams: pb().blur(10).build(), 32 | }, 33 | { 34 | label: 'Cropping', 35 | file: 'test-bucket/test-image.png', 36 | 37 | proxyParams: pb() 38 | .crop({ 39 | width: 100, 40 | height: 100, 41 | }) 42 | .build(), 43 | }, 44 | { 45 | label: 'Cropping 2', 46 | file: 'test-bucket/test-image.png', 47 | 48 | proxyParams: pb() 49 | .crop({ 50 | width: 200, 51 | height: 200, 52 | gravity: { 53 | type: GravityType.NORTHEAST, 54 | offset: { 55 | x: 10, 56 | y: 10, 57 | }, 58 | }, 59 | }) 60 | .build(), 61 | }, 62 | { 63 | label: 'Trimming', 64 | file: 'test-bucket/test-image.png', 65 | 66 | proxyParams: pb() 67 | .trim({ 68 | threshold: 0, 69 | color: '000000', 70 | }) 71 | .build(), 72 | }, 73 | { 74 | label: 'Trimming', 75 | file: 'test-bucket/test-image.png', 76 | 77 | proxyParams: pb().pad({ top: 50 }).build(), 78 | }, 79 | { 80 | label: 'Padding with background', 81 | file: 'test-bucket/test-image.png', 82 | 83 | proxyParams: pb().pad({ top: 50 }).background('ff0000').build(), 84 | }, 85 | { 86 | label: 'Resizing', 87 | file: 'test-bucket/test-image.png', 88 | width: 100, 89 | height: 100, 90 | proxyParams: pb() 91 | .resize({ 92 | type: ResizeType.FILL, 93 | width: 100, 94 | }) 95 | .build(), 96 | }, 97 | { 98 | label: 'Sharpen', 99 | file: 'test-bucket/test-image.png', 100 | proxyParams: pb().sharpen(1.5).build(), 101 | }, 102 | { 103 | label: 'PNG to JPG', 104 | file: 'test-bucket/test-image.png', 105 | proxyParams: pb().format('png').build(), 106 | }, 107 | { 108 | label: 'SVG to PNG', 109 | file: 'test-bucket/test-image.svg', 110 | proxyParams: pb().format('png').build(), 111 | }, 112 | ]; 113 | 114 | const Home: NextPage = () => { 115 | const [size, setSize] = useState(200); 116 | const sizeToggle = useRef(false); 117 | 118 | useEffect(() => { 119 | const interval = setInterval(() => { 120 | sizeToggle.current = !sizeToggle.current; 121 | setSize(sizeToggle.current ? 200 : 100); 122 | }, 3000); 123 | 124 | return () => clearInterval(interval); 125 | }, []); 126 | 127 | return ( 128 | <> 129 | 130 | Create Next App 131 | 132 |
133 |

next/image s3 imgproxy loader

134 |
135 | {demoContent.map((d, idx) => ( 136 |
137 |

{d.label}

138 |
139 | 140 |
141 |
142 | ))} 143 |
144 |
145 |
146 |

Dynamic

147 |
148 | 155 |
156 |
157 |
158 |
159 |
160 |

Background Image

161 |
172 | Lorem ipsum dolor sit amet consectetur, adipisicing elit. 173 | Consectetur ipsam voluptates velit, perferendis alias maiores 174 | atque rem accusantium culpa vero doloremque repellat porro fugiat 175 | nam ad veniam accusamus aliquid molestias. 176 |
177 |
178 |
179 |
180 | 181 | ); 182 | }; 183 | 184 | export default Home; 185 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitPatty/next-image-s3-imgproxy-loader/a4787993d28d3fafd883a36fbe7242160945229c/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | // server.js 2 | const { createServer } = require('http'); 3 | const { parse } = require('url'); 4 | const next = require('next'); 5 | 6 | const dev = process.env.NODE_ENV !== 'production'; 7 | const app = next({ dev }); 8 | const handle = app.getRequestHandler(); 9 | 10 | const imgProxy = require('@bitpatty/next-image-s3-imgproxy-loader'); 11 | 12 | app.prepare().then(() => { 13 | createServer((req, res) => { 14 | const parsedUrl = parse(req.url, true); 15 | const { pathname, query } = parsedUrl; 16 | 17 | if (pathname === imgProxy.IMGPROXY_ENDPOINT) { 18 | imgProxy.handle(new URL('http://localhost:4000/'), query, res, { 19 | signature: { 20 | key: '91bdcda48ce22cd7d8d3a0eda930b3db1762bc1cba5dc13542e723b68fe55d6f9d18199cbe35191a45faf22593405cad0fe76ffec67d24f8aee861ac8fe44d96', 21 | salt: '72456c286761260f320391fe500fcec53755958dabd288867a6db072e1bc1dbd84b15079838a83a715edc1ecad50c3ce91dd8fdef6f981816fa274f91d8ecf06', 22 | }, 23 | bucketWhitelist: ['test-bucket'], 24 | }); 25 | } else { 26 | handle(req, res, parsedUrl); 27 | } 28 | }).listen(3000, (err) => { 29 | if (err) throw err; 30 | console.log('> Ready on http://localhost:3000'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /example/styles/global.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 3 | 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; 4 | } 5 | 6 | main > div { 7 | display: flex; 8 | flex-wrap: wrap; 9 | } 10 | 11 | main > div > div { 12 | margin-left: 20px; 13 | } 14 | 15 | .imgcontainer { 16 | position: relative; 17 | height: 200px; 18 | width: 200px; 19 | } 20 | 21 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true 21 | }, 22 | "include": [ 23 | "next-env.d.ts", 24 | "**/*.ts", 25 | "**/*.tsx" 26 | ], 27 | "exclude": [ 28 | "node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bitpatty/next-image-s3-imgproxy-loader", 3 | "version": "0.13.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@bitpatty/next-image-s3-imgproxy-loader", 9 | "version": "0.13.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@bitpatty/imgproxy-url-builder": "^2.0.0" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "22.8.1", 16 | "@types/react": "19.2.2", 17 | "next": "15.5.6", 18 | "npm-check-updates": "19.1.1", 19 | "prettier": "3.6.2", 20 | "rollup": "4.52.4", 21 | "rollup-plugin-typescript2": "0.36.0", 22 | "tslib": "2.8.1", 23 | "typescript": "5.9.3" 24 | }, 25 | "peerDependencies": { 26 | "next": ">=13.0.0 <16.0.0" 27 | } 28 | }, 29 | "node_modules/@bitpatty/imgproxy-url-builder": { 30 | "version": "2.0.0", 31 | "resolved": "https://registry.npmjs.org/@bitpatty/imgproxy-url-builder/-/imgproxy-url-builder-2.0.0.tgz", 32 | "integrity": "sha512-xGNwvLIsCIVi+Q+WSKHwUB9CEAtrz/2f1H0nHBX7gOjUgK/vfGBKvlkTf6ybtNjRxgo1BHXkkFKCl8/qXbGYjQ==", 33 | "license": "MIT" 34 | }, 35 | "node_modules/@emnapi/runtime": { 36 | "version": "1.4.4", 37 | "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.4.tgz", 38 | "integrity": "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==", 39 | "dev": true, 40 | "license": "MIT", 41 | "optional": true, 42 | "dependencies": { 43 | "tslib": "^2.4.0" 44 | } 45 | }, 46 | "node_modules/@img/sharp-darwin-arm64": { 47 | "version": "0.34.3", 48 | "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz", 49 | "integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==", 50 | "cpu": [ 51 | "arm64" 52 | ], 53 | "dev": true, 54 | "license": "Apache-2.0", 55 | "optional": true, 56 | "os": [ 57 | "darwin" 58 | ], 59 | "engines": { 60 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 61 | }, 62 | "funding": { 63 | "url": "https://opencollective.com/libvips" 64 | }, 65 | "optionalDependencies": { 66 | "@img/sharp-libvips-darwin-arm64": "1.2.0" 67 | } 68 | }, 69 | "node_modules/@img/sharp-darwin-x64": { 70 | "version": "0.34.3", 71 | "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz", 72 | "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==", 73 | "cpu": [ 74 | "x64" 75 | ], 76 | "dev": true, 77 | "license": "Apache-2.0", 78 | "optional": true, 79 | "os": [ 80 | "darwin" 81 | ], 82 | "engines": { 83 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 84 | }, 85 | "funding": { 86 | "url": "https://opencollective.com/libvips" 87 | }, 88 | "optionalDependencies": { 89 | "@img/sharp-libvips-darwin-x64": "1.2.0" 90 | } 91 | }, 92 | "node_modules/@img/sharp-libvips-darwin-arm64": { 93 | "version": "1.2.0", 94 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz", 95 | "integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==", 96 | "cpu": [ 97 | "arm64" 98 | ], 99 | "dev": true, 100 | "license": "LGPL-3.0-or-later", 101 | "optional": true, 102 | "os": [ 103 | "darwin" 104 | ], 105 | "funding": { 106 | "url": "https://opencollective.com/libvips" 107 | } 108 | }, 109 | "node_modules/@img/sharp-libvips-darwin-x64": { 110 | "version": "1.2.0", 111 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz", 112 | "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==", 113 | "cpu": [ 114 | "x64" 115 | ], 116 | "dev": true, 117 | "license": "LGPL-3.0-or-later", 118 | "optional": true, 119 | "os": [ 120 | "darwin" 121 | ], 122 | "funding": { 123 | "url": "https://opencollective.com/libvips" 124 | } 125 | }, 126 | "node_modules/@img/sharp-libvips-linux-arm": { 127 | "version": "1.2.0", 128 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz", 129 | "integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==", 130 | "cpu": [ 131 | "arm" 132 | ], 133 | "dev": true, 134 | "license": "LGPL-3.0-or-later", 135 | "optional": true, 136 | "os": [ 137 | "linux" 138 | ], 139 | "funding": { 140 | "url": "https://opencollective.com/libvips" 141 | } 142 | }, 143 | "node_modules/@img/sharp-libvips-linux-arm64": { 144 | "version": "1.2.0", 145 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz", 146 | "integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==", 147 | "cpu": [ 148 | "arm64" 149 | ], 150 | "dev": true, 151 | "license": "LGPL-3.0-or-later", 152 | "optional": true, 153 | "os": [ 154 | "linux" 155 | ], 156 | "funding": { 157 | "url": "https://opencollective.com/libvips" 158 | } 159 | }, 160 | "node_modules/@img/sharp-libvips-linux-ppc64": { 161 | "version": "1.2.0", 162 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz", 163 | "integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==", 164 | "cpu": [ 165 | "ppc64" 166 | ], 167 | "dev": true, 168 | "license": "LGPL-3.0-or-later", 169 | "optional": true, 170 | "os": [ 171 | "linux" 172 | ], 173 | "funding": { 174 | "url": "https://opencollective.com/libvips" 175 | } 176 | }, 177 | "node_modules/@img/sharp-libvips-linux-s390x": { 178 | "version": "1.2.0", 179 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz", 180 | "integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==", 181 | "cpu": [ 182 | "s390x" 183 | ], 184 | "dev": true, 185 | "license": "LGPL-3.0-or-later", 186 | "optional": true, 187 | "os": [ 188 | "linux" 189 | ], 190 | "funding": { 191 | "url": "https://opencollective.com/libvips" 192 | } 193 | }, 194 | "node_modules/@img/sharp-libvips-linux-x64": { 195 | "version": "1.2.0", 196 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz", 197 | "integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==", 198 | "cpu": [ 199 | "x64" 200 | ], 201 | "dev": true, 202 | "license": "LGPL-3.0-or-later", 203 | "optional": true, 204 | "os": [ 205 | "linux" 206 | ], 207 | "funding": { 208 | "url": "https://opencollective.com/libvips" 209 | } 210 | }, 211 | "node_modules/@img/sharp-libvips-linuxmusl-arm64": { 212 | "version": "1.2.0", 213 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz", 214 | "integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==", 215 | "cpu": [ 216 | "arm64" 217 | ], 218 | "dev": true, 219 | "license": "LGPL-3.0-or-later", 220 | "optional": true, 221 | "os": [ 222 | "linux" 223 | ], 224 | "funding": { 225 | "url": "https://opencollective.com/libvips" 226 | } 227 | }, 228 | "node_modules/@img/sharp-libvips-linuxmusl-x64": { 229 | "version": "1.2.0", 230 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz", 231 | "integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==", 232 | "cpu": [ 233 | "x64" 234 | ], 235 | "dev": true, 236 | "license": "LGPL-3.0-or-later", 237 | "optional": true, 238 | "os": [ 239 | "linux" 240 | ], 241 | "funding": { 242 | "url": "https://opencollective.com/libvips" 243 | } 244 | }, 245 | "node_modules/@img/sharp-linux-arm": { 246 | "version": "0.34.3", 247 | "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz", 248 | "integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==", 249 | "cpu": [ 250 | "arm" 251 | ], 252 | "dev": true, 253 | "license": "Apache-2.0", 254 | "optional": true, 255 | "os": [ 256 | "linux" 257 | ], 258 | "engines": { 259 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 260 | }, 261 | "funding": { 262 | "url": "https://opencollective.com/libvips" 263 | }, 264 | "optionalDependencies": { 265 | "@img/sharp-libvips-linux-arm": "1.2.0" 266 | } 267 | }, 268 | "node_modules/@img/sharp-linux-arm64": { 269 | "version": "0.34.3", 270 | "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz", 271 | "integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==", 272 | "cpu": [ 273 | "arm64" 274 | ], 275 | "dev": true, 276 | "license": "Apache-2.0", 277 | "optional": true, 278 | "os": [ 279 | "linux" 280 | ], 281 | "engines": { 282 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 283 | }, 284 | "funding": { 285 | "url": "https://opencollective.com/libvips" 286 | }, 287 | "optionalDependencies": { 288 | "@img/sharp-libvips-linux-arm64": "1.2.0" 289 | } 290 | }, 291 | "node_modules/@img/sharp-linux-ppc64": { 292 | "version": "0.34.3", 293 | "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz", 294 | "integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==", 295 | "cpu": [ 296 | "ppc64" 297 | ], 298 | "dev": true, 299 | "license": "Apache-2.0", 300 | "optional": true, 301 | "os": [ 302 | "linux" 303 | ], 304 | "engines": { 305 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 306 | }, 307 | "funding": { 308 | "url": "https://opencollective.com/libvips" 309 | }, 310 | "optionalDependencies": { 311 | "@img/sharp-libvips-linux-ppc64": "1.2.0" 312 | } 313 | }, 314 | "node_modules/@img/sharp-linux-s390x": { 315 | "version": "0.34.3", 316 | "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz", 317 | "integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==", 318 | "cpu": [ 319 | "s390x" 320 | ], 321 | "dev": true, 322 | "license": "Apache-2.0", 323 | "optional": true, 324 | "os": [ 325 | "linux" 326 | ], 327 | "engines": { 328 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 329 | }, 330 | "funding": { 331 | "url": "https://opencollective.com/libvips" 332 | }, 333 | "optionalDependencies": { 334 | "@img/sharp-libvips-linux-s390x": "1.2.0" 335 | } 336 | }, 337 | "node_modules/@img/sharp-linux-x64": { 338 | "version": "0.34.3", 339 | "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz", 340 | "integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==", 341 | "cpu": [ 342 | "x64" 343 | ], 344 | "dev": true, 345 | "license": "Apache-2.0", 346 | "optional": true, 347 | "os": [ 348 | "linux" 349 | ], 350 | "engines": { 351 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 352 | }, 353 | "funding": { 354 | "url": "https://opencollective.com/libvips" 355 | }, 356 | "optionalDependencies": { 357 | "@img/sharp-libvips-linux-x64": "1.2.0" 358 | } 359 | }, 360 | "node_modules/@img/sharp-linuxmusl-arm64": { 361 | "version": "0.34.3", 362 | "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz", 363 | "integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==", 364 | "cpu": [ 365 | "arm64" 366 | ], 367 | "dev": true, 368 | "license": "Apache-2.0", 369 | "optional": true, 370 | "os": [ 371 | "linux" 372 | ], 373 | "engines": { 374 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 375 | }, 376 | "funding": { 377 | "url": "https://opencollective.com/libvips" 378 | }, 379 | "optionalDependencies": { 380 | "@img/sharp-libvips-linuxmusl-arm64": "1.2.0" 381 | } 382 | }, 383 | "node_modules/@img/sharp-linuxmusl-x64": { 384 | "version": "0.34.3", 385 | "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz", 386 | "integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==", 387 | "cpu": [ 388 | "x64" 389 | ], 390 | "dev": true, 391 | "license": "Apache-2.0", 392 | "optional": true, 393 | "os": [ 394 | "linux" 395 | ], 396 | "engines": { 397 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 398 | }, 399 | "funding": { 400 | "url": "https://opencollective.com/libvips" 401 | }, 402 | "optionalDependencies": { 403 | "@img/sharp-libvips-linuxmusl-x64": "1.2.0" 404 | } 405 | }, 406 | "node_modules/@img/sharp-wasm32": { 407 | "version": "0.34.3", 408 | "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz", 409 | "integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==", 410 | "cpu": [ 411 | "wasm32" 412 | ], 413 | "dev": true, 414 | "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", 415 | "optional": true, 416 | "dependencies": { 417 | "@emnapi/runtime": "^1.4.4" 418 | }, 419 | "engines": { 420 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 421 | }, 422 | "funding": { 423 | "url": "https://opencollective.com/libvips" 424 | } 425 | }, 426 | "node_modules/@img/sharp-win32-arm64": { 427 | "version": "0.34.3", 428 | "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz", 429 | "integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==", 430 | "cpu": [ 431 | "arm64" 432 | ], 433 | "dev": true, 434 | "license": "Apache-2.0 AND LGPL-3.0-or-later", 435 | "optional": true, 436 | "os": [ 437 | "win32" 438 | ], 439 | "engines": { 440 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 441 | }, 442 | "funding": { 443 | "url": "https://opencollective.com/libvips" 444 | } 445 | }, 446 | "node_modules/@img/sharp-win32-ia32": { 447 | "version": "0.34.3", 448 | "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz", 449 | "integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==", 450 | "cpu": [ 451 | "ia32" 452 | ], 453 | "dev": true, 454 | "license": "Apache-2.0 AND LGPL-3.0-or-later", 455 | "optional": true, 456 | "os": [ 457 | "win32" 458 | ], 459 | "engines": { 460 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 461 | }, 462 | "funding": { 463 | "url": "https://opencollective.com/libvips" 464 | } 465 | }, 466 | "node_modules/@img/sharp-win32-x64": { 467 | "version": "0.34.3", 468 | "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz", 469 | "integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==", 470 | "cpu": [ 471 | "x64" 472 | ], 473 | "dev": true, 474 | "license": "Apache-2.0 AND LGPL-3.0-or-later", 475 | "optional": true, 476 | "os": [ 477 | "win32" 478 | ], 479 | "engines": { 480 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 481 | }, 482 | "funding": { 483 | "url": "https://opencollective.com/libvips" 484 | } 485 | }, 486 | "node_modules/@next/env": { 487 | "version": "15.5.6", 488 | "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.6.tgz", 489 | "integrity": "sha512-3qBGRW+sCGzgbpc5TS1a0p7eNxnOarGVQhZxfvTdnV0gFI61lX7QNtQ4V1TSREctXzYn5NetbUsLvyqwLFJM6Q==", 490 | "dev": true, 491 | "license": "MIT" 492 | }, 493 | "node_modules/@next/swc-darwin-arm64": { 494 | "version": "15.5.6", 495 | "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.6.tgz", 496 | "integrity": "sha512-ES3nRz7N+L5Umz4KoGfZ4XX6gwHplwPhioVRc25+QNsDa7RtUF/z8wJcbuQ2Tffm5RZwuN2A063eapoJ1u4nPg==", 497 | "cpu": [ 498 | "arm64" 499 | ], 500 | "dev": true, 501 | "license": "MIT", 502 | "optional": true, 503 | "os": [ 504 | "darwin" 505 | ], 506 | "engines": { 507 | "node": ">= 10" 508 | } 509 | }, 510 | "node_modules/@next/swc-darwin-x64": { 511 | "version": "15.5.6", 512 | "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.6.tgz", 513 | "integrity": "sha512-JIGcytAyk9LQp2/nuVZPAtj8uaJ/zZhsKOASTjxDug0SPU9LAM3wy6nPU735M1OqacR4U20LHVF5v5Wnl9ptTA==", 514 | "cpu": [ 515 | "x64" 516 | ], 517 | "dev": true, 518 | "license": "MIT", 519 | "optional": true, 520 | "os": [ 521 | "darwin" 522 | ], 523 | "engines": { 524 | "node": ">= 10" 525 | } 526 | }, 527 | "node_modules/@next/swc-linux-arm64-gnu": { 528 | "version": "15.5.6", 529 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.6.tgz", 530 | "integrity": "sha512-qvz4SVKQ0P3/Im9zcS2RmfFL/UCQnsJKJwQSkissbngnB/12c6bZTCB0gHTexz1s6d/mD0+egPKXAIRFVS7hQg==", 531 | "cpu": [ 532 | "arm64" 533 | ], 534 | "dev": true, 535 | "license": "MIT", 536 | "optional": true, 537 | "os": [ 538 | "linux" 539 | ], 540 | "engines": { 541 | "node": ">= 10" 542 | } 543 | }, 544 | "node_modules/@next/swc-linux-arm64-musl": { 545 | "version": "15.5.6", 546 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.6.tgz", 547 | "integrity": "sha512-FsbGVw3SJz1hZlvnWD+T6GFgV9/NYDeLTNQB2MXoPN5u9VA9OEDy6fJEfePfsUKAhJufFbZLgp0cPxMuV6SV0w==", 548 | "cpu": [ 549 | "arm64" 550 | ], 551 | "dev": true, 552 | "license": "MIT", 553 | "optional": true, 554 | "os": [ 555 | "linux" 556 | ], 557 | "engines": { 558 | "node": ">= 10" 559 | } 560 | }, 561 | "node_modules/@next/swc-linux-x64-gnu": { 562 | "version": "15.5.6", 563 | "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.6.tgz", 564 | "integrity": "sha512-3QnHGFWlnvAgyxFxt2Ny8PTpXtQD7kVEeaFat5oPAHHI192WKYB+VIKZijtHLGdBBvc16tiAkPTDmQNOQ0dyrA==", 565 | "cpu": [ 566 | "x64" 567 | ], 568 | "dev": true, 569 | "license": "MIT", 570 | "optional": true, 571 | "os": [ 572 | "linux" 573 | ], 574 | "engines": { 575 | "node": ">= 10" 576 | } 577 | }, 578 | "node_modules/@next/swc-linux-x64-musl": { 579 | "version": "15.5.6", 580 | "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.6.tgz", 581 | "integrity": "sha512-OsGX148sL+TqMK9YFaPFPoIaJKbFJJxFzkXZljIgA9hjMjdruKht6xDCEv1HLtlLNfkx3c5w2GLKhj7veBQizQ==", 582 | "cpu": [ 583 | "x64" 584 | ], 585 | "dev": true, 586 | "license": "MIT", 587 | "optional": true, 588 | "os": [ 589 | "linux" 590 | ], 591 | "engines": { 592 | "node": ">= 10" 593 | } 594 | }, 595 | "node_modules/@next/swc-win32-arm64-msvc": { 596 | "version": "15.5.6", 597 | "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.6.tgz", 598 | "integrity": "sha512-ONOMrqWxdzXDJNh2n60H6gGyKed42Ieu6UTVPZteXpuKbLZTH4G4eBMsr5qWgOBA+s7F+uB4OJbZnrkEDnZ5Fg==", 599 | "cpu": [ 600 | "arm64" 601 | ], 602 | "dev": true, 603 | "license": "MIT", 604 | "optional": true, 605 | "os": [ 606 | "win32" 607 | ], 608 | "engines": { 609 | "node": ">= 10" 610 | } 611 | }, 612 | "node_modules/@next/swc-win32-x64-msvc": { 613 | "version": "15.5.6", 614 | "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.6.tgz", 615 | "integrity": "sha512-pxK4VIjFRx1MY92UycLOOw7dTdvccWsNETQ0kDHkBlcFH1GrTLUjSiHU1ohrznnux6TqRHgv5oflhfIWZwVROQ==", 616 | "cpu": [ 617 | "x64" 618 | ], 619 | "dev": true, 620 | "license": "MIT", 621 | "optional": true, 622 | "os": [ 623 | "win32" 624 | ], 625 | "engines": { 626 | "node": ">= 10" 627 | } 628 | }, 629 | "node_modules/@rollup/pluginutils": { 630 | "version": "4.2.1", 631 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", 632 | "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", 633 | "dev": true, 634 | "license": "MIT", 635 | "dependencies": { 636 | "estree-walker": "^2.0.1", 637 | "picomatch": "^2.2.2" 638 | }, 639 | "engines": { 640 | "node": ">= 8.0.0" 641 | } 642 | }, 643 | "node_modules/@rollup/rollup-android-arm-eabi": { 644 | "version": "4.52.4", 645 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", 646 | "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", 647 | "cpu": [ 648 | "arm" 649 | ], 650 | "dev": true, 651 | "license": "MIT", 652 | "optional": true, 653 | "os": [ 654 | "android" 655 | ] 656 | }, 657 | "node_modules/@rollup/rollup-android-arm64": { 658 | "version": "4.52.4", 659 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", 660 | "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", 661 | "cpu": [ 662 | "arm64" 663 | ], 664 | "dev": true, 665 | "license": "MIT", 666 | "optional": true, 667 | "os": [ 668 | "android" 669 | ] 670 | }, 671 | "node_modules/@rollup/rollup-darwin-arm64": { 672 | "version": "4.52.4", 673 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", 674 | "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", 675 | "cpu": [ 676 | "arm64" 677 | ], 678 | "dev": true, 679 | "license": "MIT", 680 | "optional": true, 681 | "os": [ 682 | "darwin" 683 | ] 684 | }, 685 | "node_modules/@rollup/rollup-darwin-x64": { 686 | "version": "4.52.4", 687 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", 688 | "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", 689 | "cpu": [ 690 | "x64" 691 | ], 692 | "dev": true, 693 | "license": "MIT", 694 | "optional": true, 695 | "os": [ 696 | "darwin" 697 | ] 698 | }, 699 | "node_modules/@rollup/rollup-freebsd-arm64": { 700 | "version": "4.52.4", 701 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", 702 | "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", 703 | "cpu": [ 704 | "arm64" 705 | ], 706 | "dev": true, 707 | "license": "MIT", 708 | "optional": true, 709 | "os": [ 710 | "freebsd" 711 | ] 712 | }, 713 | "node_modules/@rollup/rollup-freebsd-x64": { 714 | "version": "4.52.4", 715 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", 716 | "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", 717 | "cpu": [ 718 | "x64" 719 | ], 720 | "dev": true, 721 | "license": "MIT", 722 | "optional": true, 723 | "os": [ 724 | "freebsd" 725 | ] 726 | }, 727 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 728 | "version": "4.52.4", 729 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", 730 | "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", 731 | "cpu": [ 732 | "arm" 733 | ], 734 | "dev": true, 735 | "license": "MIT", 736 | "optional": true, 737 | "os": [ 738 | "linux" 739 | ] 740 | }, 741 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 742 | "version": "4.52.4", 743 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", 744 | "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", 745 | "cpu": [ 746 | "arm" 747 | ], 748 | "dev": true, 749 | "license": "MIT", 750 | "optional": true, 751 | "os": [ 752 | "linux" 753 | ] 754 | }, 755 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 756 | "version": "4.52.4", 757 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", 758 | "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", 759 | "cpu": [ 760 | "arm64" 761 | ], 762 | "dev": true, 763 | "license": "MIT", 764 | "optional": true, 765 | "os": [ 766 | "linux" 767 | ] 768 | }, 769 | "node_modules/@rollup/rollup-linux-arm64-musl": { 770 | "version": "4.52.4", 771 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", 772 | "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", 773 | "cpu": [ 774 | "arm64" 775 | ], 776 | "dev": true, 777 | "license": "MIT", 778 | "optional": true, 779 | "os": [ 780 | "linux" 781 | ] 782 | }, 783 | "node_modules/@rollup/rollup-linux-loong64-gnu": { 784 | "version": "4.52.4", 785 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", 786 | "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", 787 | "cpu": [ 788 | "loong64" 789 | ], 790 | "dev": true, 791 | "license": "MIT", 792 | "optional": true, 793 | "os": [ 794 | "linux" 795 | ] 796 | }, 797 | "node_modules/@rollup/rollup-linux-ppc64-gnu": { 798 | "version": "4.52.4", 799 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", 800 | "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", 801 | "cpu": [ 802 | "ppc64" 803 | ], 804 | "dev": true, 805 | "license": "MIT", 806 | "optional": true, 807 | "os": [ 808 | "linux" 809 | ] 810 | }, 811 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 812 | "version": "4.52.4", 813 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", 814 | "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", 815 | "cpu": [ 816 | "riscv64" 817 | ], 818 | "dev": true, 819 | "license": "MIT", 820 | "optional": true, 821 | "os": [ 822 | "linux" 823 | ] 824 | }, 825 | "node_modules/@rollup/rollup-linux-riscv64-musl": { 826 | "version": "4.52.4", 827 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", 828 | "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", 829 | "cpu": [ 830 | "riscv64" 831 | ], 832 | "dev": true, 833 | "license": "MIT", 834 | "optional": true, 835 | "os": [ 836 | "linux" 837 | ] 838 | }, 839 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 840 | "version": "4.52.4", 841 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", 842 | "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", 843 | "cpu": [ 844 | "s390x" 845 | ], 846 | "dev": true, 847 | "license": "MIT", 848 | "optional": true, 849 | "os": [ 850 | "linux" 851 | ] 852 | }, 853 | "node_modules/@rollup/rollup-linux-x64-gnu": { 854 | "version": "4.52.4", 855 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", 856 | "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", 857 | "cpu": [ 858 | "x64" 859 | ], 860 | "dev": true, 861 | "license": "MIT", 862 | "optional": true, 863 | "os": [ 864 | "linux" 865 | ] 866 | }, 867 | "node_modules/@rollup/rollup-linux-x64-musl": { 868 | "version": "4.52.4", 869 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", 870 | "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", 871 | "cpu": [ 872 | "x64" 873 | ], 874 | "dev": true, 875 | "license": "MIT", 876 | "optional": true, 877 | "os": [ 878 | "linux" 879 | ] 880 | }, 881 | "node_modules/@rollup/rollup-openharmony-arm64": { 882 | "version": "4.52.4", 883 | "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", 884 | "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", 885 | "cpu": [ 886 | "arm64" 887 | ], 888 | "dev": true, 889 | "license": "MIT", 890 | "optional": true, 891 | "os": [ 892 | "openharmony" 893 | ] 894 | }, 895 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 896 | "version": "4.52.4", 897 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", 898 | "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", 899 | "cpu": [ 900 | "arm64" 901 | ], 902 | "dev": true, 903 | "license": "MIT", 904 | "optional": true, 905 | "os": [ 906 | "win32" 907 | ] 908 | }, 909 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 910 | "version": "4.52.4", 911 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", 912 | "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", 913 | "cpu": [ 914 | "ia32" 915 | ], 916 | "dev": true, 917 | "license": "MIT", 918 | "optional": true, 919 | "os": [ 920 | "win32" 921 | ] 922 | }, 923 | "node_modules/@rollup/rollup-win32-x64-gnu": { 924 | "version": "4.52.4", 925 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", 926 | "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", 927 | "cpu": [ 928 | "x64" 929 | ], 930 | "dev": true, 931 | "license": "MIT", 932 | "optional": true, 933 | "os": [ 934 | "win32" 935 | ] 936 | }, 937 | "node_modules/@rollup/rollup-win32-x64-msvc": { 938 | "version": "4.52.4", 939 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", 940 | "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", 941 | "cpu": [ 942 | "x64" 943 | ], 944 | "dev": true, 945 | "license": "MIT", 946 | "optional": true, 947 | "os": [ 948 | "win32" 949 | ] 950 | }, 951 | "node_modules/@swc/helpers": { 952 | "version": "0.5.15", 953 | "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", 954 | "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", 955 | "dev": true, 956 | "license": "Apache-2.0", 957 | "dependencies": { 958 | "tslib": "^2.8.0" 959 | } 960 | }, 961 | "node_modules/@types/estree": { 962 | "version": "1.0.8", 963 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 964 | "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 965 | "dev": true, 966 | "license": "MIT" 967 | }, 968 | "node_modules/@types/node": { 969 | "version": "22.8.1", 970 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", 971 | "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", 972 | "dev": true, 973 | "license": "MIT", 974 | "dependencies": { 975 | "undici-types": "~6.19.8" 976 | } 977 | }, 978 | "node_modules/@types/react": { 979 | "version": "19.2.2", 980 | "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", 981 | "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", 982 | "dev": true, 983 | "license": "MIT", 984 | "dependencies": { 985 | "csstype": "^3.0.2" 986 | } 987 | }, 988 | "node_modules/caniuse-lite": { 989 | "version": "1.0.30001671", 990 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001671.tgz", 991 | "integrity": "sha512-jocyVaSSfXg2faluE6hrWkMgDOiULBMca4QLtDT39hw1YxaIPHWc1CcTCKkPmHgGH6tKji6ZNbMSmUAvENf2/A==", 992 | "dev": true, 993 | "funding": [ 994 | { 995 | "type": "opencollective", 996 | "url": "https://opencollective.com/browserslist" 997 | }, 998 | { 999 | "type": "tidelift", 1000 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 1001 | }, 1002 | { 1003 | "type": "github", 1004 | "url": "https://github.com/sponsors/ai" 1005 | } 1006 | ], 1007 | "license": "CC-BY-4.0" 1008 | }, 1009 | "node_modules/client-only": { 1010 | "version": "0.0.1", 1011 | "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", 1012 | "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", 1013 | "dev": true, 1014 | "license": "MIT" 1015 | }, 1016 | "node_modules/color": { 1017 | "version": "4.2.3", 1018 | "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", 1019 | "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", 1020 | "dev": true, 1021 | "license": "MIT", 1022 | "optional": true, 1023 | "dependencies": { 1024 | "color-convert": "^2.0.1", 1025 | "color-string": "^1.9.0" 1026 | }, 1027 | "engines": { 1028 | "node": ">=12.5.0" 1029 | } 1030 | }, 1031 | "node_modules/color-convert": { 1032 | "version": "2.0.1", 1033 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1034 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1035 | "dev": true, 1036 | "license": "MIT", 1037 | "optional": true, 1038 | "dependencies": { 1039 | "color-name": "~1.1.4" 1040 | }, 1041 | "engines": { 1042 | "node": ">=7.0.0" 1043 | } 1044 | }, 1045 | "node_modules/color-name": { 1046 | "version": "1.1.4", 1047 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1048 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1049 | "dev": true, 1050 | "license": "MIT", 1051 | "optional": true 1052 | }, 1053 | "node_modules/color-string": { 1054 | "version": "1.9.1", 1055 | "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", 1056 | "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 1057 | "dev": true, 1058 | "license": "MIT", 1059 | "optional": true, 1060 | "dependencies": { 1061 | "color-name": "^1.0.0", 1062 | "simple-swizzle": "^0.2.2" 1063 | } 1064 | }, 1065 | "node_modules/commondir": { 1066 | "version": "1.0.1", 1067 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", 1068 | "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", 1069 | "dev": true, 1070 | "license": "MIT" 1071 | }, 1072 | "node_modules/csstype": { 1073 | "version": "3.1.3", 1074 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 1075 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 1076 | "dev": true, 1077 | "license": "MIT" 1078 | }, 1079 | "node_modules/detect-libc": { 1080 | "version": "2.0.4", 1081 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", 1082 | "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", 1083 | "dev": true, 1084 | "license": "Apache-2.0", 1085 | "optional": true, 1086 | "engines": { 1087 | "node": ">=8" 1088 | } 1089 | }, 1090 | "node_modules/estree-walker": { 1091 | "version": "2.0.2", 1092 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1093 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 1094 | "dev": true, 1095 | "license": "MIT" 1096 | }, 1097 | "node_modules/find-cache-dir": { 1098 | "version": "3.3.2", 1099 | "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", 1100 | "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", 1101 | "dev": true, 1102 | "license": "MIT", 1103 | "dependencies": { 1104 | "commondir": "^1.0.1", 1105 | "make-dir": "^3.0.2", 1106 | "pkg-dir": "^4.1.0" 1107 | }, 1108 | "engines": { 1109 | "node": ">=8" 1110 | }, 1111 | "funding": { 1112 | "url": "https://github.com/avajs/find-cache-dir?sponsor=1" 1113 | } 1114 | }, 1115 | "node_modules/fs-extra": { 1116 | "version": "10.1.0", 1117 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", 1118 | "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", 1119 | "dev": true, 1120 | "license": "MIT", 1121 | "dependencies": { 1122 | "graceful-fs": "^4.2.0", 1123 | "jsonfile": "^6.0.1", 1124 | "universalify": "^2.0.0" 1125 | }, 1126 | "engines": { 1127 | "node": ">=12" 1128 | } 1129 | }, 1130 | "node_modules/fsevents": { 1131 | "version": "2.3.3", 1132 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1133 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1134 | "dev": true, 1135 | "hasInstallScript": true, 1136 | "license": "MIT", 1137 | "optional": true, 1138 | "os": [ 1139 | "darwin" 1140 | ], 1141 | "engines": { 1142 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1143 | } 1144 | }, 1145 | "node_modules/graceful-fs": { 1146 | "version": "4.2.11", 1147 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 1148 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 1149 | "dev": true, 1150 | "license": "ISC" 1151 | }, 1152 | "node_modules/is-arrayish": { 1153 | "version": "0.3.2", 1154 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", 1155 | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", 1156 | "dev": true, 1157 | "license": "MIT", 1158 | "optional": true 1159 | }, 1160 | "node_modules/js-tokens": { 1161 | "version": "4.0.0", 1162 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1163 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 1164 | "dev": true, 1165 | "license": "MIT", 1166 | "peer": true 1167 | }, 1168 | "node_modules/jsonfile": { 1169 | "version": "6.1.0", 1170 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", 1171 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", 1172 | "dev": true, 1173 | "license": "MIT", 1174 | "dependencies": { 1175 | "universalify": "^2.0.0" 1176 | }, 1177 | "optionalDependencies": { 1178 | "graceful-fs": "^4.1.6" 1179 | } 1180 | }, 1181 | "node_modules/loose-envify": { 1182 | "version": "1.4.0", 1183 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 1184 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 1185 | "dev": true, 1186 | "license": "MIT", 1187 | "peer": true, 1188 | "dependencies": { 1189 | "js-tokens": "^3.0.0 || ^4.0.0" 1190 | }, 1191 | "bin": { 1192 | "loose-envify": "cli.js" 1193 | } 1194 | }, 1195 | "node_modules/make-dir": { 1196 | "version": "3.1.0", 1197 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 1198 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 1199 | "dev": true, 1200 | "license": "MIT", 1201 | "dependencies": { 1202 | "semver": "^6.0.0" 1203 | }, 1204 | "engines": { 1205 | "node": ">=8" 1206 | }, 1207 | "funding": { 1208 | "url": "https://github.com/sponsors/sindresorhus" 1209 | } 1210 | }, 1211 | "node_modules/make-dir/node_modules/semver": { 1212 | "version": "6.3.1", 1213 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 1214 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 1215 | "dev": true, 1216 | "license": "ISC", 1217 | "bin": { 1218 | "semver": "bin/semver.js" 1219 | } 1220 | }, 1221 | "node_modules/nanoid": { 1222 | "version": "3.3.7", 1223 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 1224 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 1225 | "dev": true, 1226 | "funding": [ 1227 | { 1228 | "type": "github", 1229 | "url": "https://github.com/sponsors/ai" 1230 | } 1231 | ], 1232 | "license": "MIT", 1233 | "bin": { 1234 | "nanoid": "bin/nanoid.cjs" 1235 | }, 1236 | "engines": { 1237 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1238 | } 1239 | }, 1240 | "node_modules/next": { 1241 | "version": "15.5.6", 1242 | "resolved": "https://registry.npmjs.org/next/-/next-15.5.6.tgz", 1243 | "integrity": "sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ==", 1244 | "dev": true, 1245 | "license": "MIT", 1246 | "dependencies": { 1247 | "@next/env": "15.5.6", 1248 | "@swc/helpers": "0.5.15", 1249 | "caniuse-lite": "^1.0.30001579", 1250 | "postcss": "8.4.31", 1251 | "styled-jsx": "5.1.6" 1252 | }, 1253 | "bin": { 1254 | "next": "dist/bin/next" 1255 | }, 1256 | "engines": { 1257 | "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" 1258 | }, 1259 | "optionalDependencies": { 1260 | "@next/swc-darwin-arm64": "15.5.6", 1261 | "@next/swc-darwin-x64": "15.5.6", 1262 | "@next/swc-linux-arm64-gnu": "15.5.6", 1263 | "@next/swc-linux-arm64-musl": "15.5.6", 1264 | "@next/swc-linux-x64-gnu": "15.5.6", 1265 | "@next/swc-linux-x64-musl": "15.5.6", 1266 | "@next/swc-win32-arm64-msvc": "15.5.6", 1267 | "@next/swc-win32-x64-msvc": "15.5.6", 1268 | "sharp": "^0.34.3" 1269 | }, 1270 | "peerDependencies": { 1271 | "@opentelemetry/api": "^1.1.0", 1272 | "@playwright/test": "^1.51.1", 1273 | "babel-plugin-react-compiler": "*", 1274 | "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", 1275 | "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", 1276 | "sass": "^1.3.0" 1277 | }, 1278 | "peerDependenciesMeta": { 1279 | "@opentelemetry/api": { 1280 | "optional": true 1281 | }, 1282 | "@playwright/test": { 1283 | "optional": true 1284 | }, 1285 | "babel-plugin-react-compiler": { 1286 | "optional": true 1287 | }, 1288 | "sass": { 1289 | "optional": true 1290 | } 1291 | } 1292 | }, 1293 | "node_modules/npm-check-updates": { 1294 | "version": "19.1.1", 1295 | "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-19.1.1.tgz", 1296 | "integrity": "sha512-vy/uNbaK6Xfj/QzM8OXeALZak67E0uHjUlbdT1YGy4bdj0xlBU6AVd+8bscY8vlDpyzL6Y7mxcrX8kzEDeEpNg==", 1297 | "dev": true, 1298 | "license": "Apache-2.0", 1299 | "bin": { 1300 | "ncu": "build/cli.js", 1301 | "npm-check-updates": "build/cli.js" 1302 | }, 1303 | "engines": { 1304 | "node": ">=20.0.0", 1305 | "npm": ">=8.12.1" 1306 | } 1307 | }, 1308 | "node_modules/p-try": { 1309 | "version": "2.2.0", 1310 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 1311 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 1312 | "dev": true, 1313 | "license": "MIT", 1314 | "engines": { 1315 | "node": ">=6" 1316 | } 1317 | }, 1318 | "node_modules/path-exists": { 1319 | "version": "4.0.0", 1320 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1321 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 1322 | "dev": true, 1323 | "license": "MIT", 1324 | "engines": { 1325 | "node": ">=8" 1326 | } 1327 | }, 1328 | "node_modules/picocolors": { 1329 | "version": "1.1.1", 1330 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1331 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 1332 | "dev": true, 1333 | "license": "ISC" 1334 | }, 1335 | "node_modules/picomatch": { 1336 | "version": "2.3.1", 1337 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1338 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1339 | "dev": true, 1340 | "license": "MIT", 1341 | "engines": { 1342 | "node": ">=8.6" 1343 | }, 1344 | "funding": { 1345 | "url": "https://github.com/sponsors/jonschlinkert" 1346 | } 1347 | }, 1348 | "node_modules/pkg-dir": { 1349 | "version": "4.2.0", 1350 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", 1351 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", 1352 | "dev": true, 1353 | "license": "MIT", 1354 | "dependencies": { 1355 | "find-up": "^4.0.0" 1356 | }, 1357 | "engines": { 1358 | "node": ">=8" 1359 | } 1360 | }, 1361 | "node_modules/pkg-dir/node_modules/find-up": { 1362 | "version": "4.1.0", 1363 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 1364 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 1365 | "dev": true, 1366 | "license": "MIT", 1367 | "dependencies": { 1368 | "locate-path": "^5.0.0", 1369 | "path-exists": "^4.0.0" 1370 | }, 1371 | "engines": { 1372 | "node": ">=8" 1373 | } 1374 | }, 1375 | "node_modules/pkg-dir/node_modules/locate-path": { 1376 | "version": "5.0.0", 1377 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 1378 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 1379 | "dev": true, 1380 | "license": "MIT", 1381 | "dependencies": { 1382 | "p-locate": "^4.1.0" 1383 | }, 1384 | "engines": { 1385 | "node": ">=8" 1386 | } 1387 | }, 1388 | "node_modules/pkg-dir/node_modules/p-limit": { 1389 | "version": "2.3.0", 1390 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 1391 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 1392 | "dev": true, 1393 | "license": "MIT", 1394 | "dependencies": { 1395 | "p-try": "^2.0.0" 1396 | }, 1397 | "engines": { 1398 | "node": ">=6" 1399 | }, 1400 | "funding": { 1401 | "url": "https://github.com/sponsors/sindresorhus" 1402 | } 1403 | }, 1404 | "node_modules/pkg-dir/node_modules/p-locate": { 1405 | "version": "4.1.0", 1406 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 1407 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 1408 | "dev": true, 1409 | "license": "MIT", 1410 | "dependencies": { 1411 | "p-limit": "^2.2.0" 1412 | }, 1413 | "engines": { 1414 | "node": ">=8" 1415 | } 1416 | }, 1417 | "node_modules/postcss": { 1418 | "version": "8.4.31", 1419 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", 1420 | "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", 1421 | "dev": true, 1422 | "funding": [ 1423 | { 1424 | "type": "opencollective", 1425 | "url": "https://opencollective.com/postcss/" 1426 | }, 1427 | { 1428 | "type": "tidelift", 1429 | "url": "https://tidelift.com/funding/github/npm/postcss" 1430 | }, 1431 | { 1432 | "type": "github", 1433 | "url": "https://github.com/sponsors/ai" 1434 | } 1435 | ], 1436 | "license": "MIT", 1437 | "dependencies": { 1438 | "nanoid": "^3.3.6", 1439 | "picocolors": "^1.0.0", 1440 | "source-map-js": "^1.0.2" 1441 | }, 1442 | "engines": { 1443 | "node": "^10 || ^12 || >=14" 1444 | } 1445 | }, 1446 | "node_modules/prettier": { 1447 | "version": "3.6.2", 1448 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", 1449 | "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", 1450 | "dev": true, 1451 | "bin": { 1452 | "prettier": "bin/prettier.cjs" 1453 | }, 1454 | "engines": { 1455 | "node": ">=14" 1456 | }, 1457 | "funding": { 1458 | "url": "https://github.com/prettier/prettier?sponsor=1" 1459 | } 1460 | }, 1461 | "node_modules/react": { 1462 | "version": "18.3.1", 1463 | "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", 1464 | "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", 1465 | "dev": true, 1466 | "license": "MIT", 1467 | "peer": true, 1468 | "dependencies": { 1469 | "loose-envify": "^1.1.0" 1470 | }, 1471 | "engines": { 1472 | "node": ">=0.10.0" 1473 | } 1474 | }, 1475 | "node_modules/react-dom": { 1476 | "version": "18.3.1", 1477 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", 1478 | "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", 1479 | "dev": true, 1480 | "license": "MIT", 1481 | "peer": true, 1482 | "dependencies": { 1483 | "loose-envify": "^1.1.0", 1484 | "scheduler": "^0.23.2" 1485 | }, 1486 | "peerDependencies": { 1487 | "react": "^18.3.1" 1488 | } 1489 | }, 1490 | "node_modules/rollup": { 1491 | "version": "4.52.4", 1492 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", 1493 | "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", 1494 | "dev": true, 1495 | "license": "MIT", 1496 | "dependencies": { 1497 | "@types/estree": "1.0.8" 1498 | }, 1499 | "bin": { 1500 | "rollup": "dist/bin/rollup" 1501 | }, 1502 | "engines": { 1503 | "node": ">=18.0.0", 1504 | "npm": ">=8.0.0" 1505 | }, 1506 | "optionalDependencies": { 1507 | "@rollup/rollup-android-arm-eabi": "4.52.4", 1508 | "@rollup/rollup-android-arm64": "4.52.4", 1509 | "@rollup/rollup-darwin-arm64": "4.52.4", 1510 | "@rollup/rollup-darwin-x64": "4.52.4", 1511 | "@rollup/rollup-freebsd-arm64": "4.52.4", 1512 | "@rollup/rollup-freebsd-x64": "4.52.4", 1513 | "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", 1514 | "@rollup/rollup-linux-arm-musleabihf": "4.52.4", 1515 | "@rollup/rollup-linux-arm64-gnu": "4.52.4", 1516 | "@rollup/rollup-linux-arm64-musl": "4.52.4", 1517 | "@rollup/rollup-linux-loong64-gnu": "4.52.4", 1518 | "@rollup/rollup-linux-ppc64-gnu": "4.52.4", 1519 | "@rollup/rollup-linux-riscv64-gnu": "4.52.4", 1520 | "@rollup/rollup-linux-riscv64-musl": "4.52.4", 1521 | "@rollup/rollup-linux-s390x-gnu": "4.52.4", 1522 | "@rollup/rollup-linux-x64-gnu": "4.52.4", 1523 | "@rollup/rollup-linux-x64-musl": "4.52.4", 1524 | "@rollup/rollup-openharmony-arm64": "4.52.4", 1525 | "@rollup/rollup-win32-arm64-msvc": "4.52.4", 1526 | "@rollup/rollup-win32-ia32-msvc": "4.52.4", 1527 | "@rollup/rollup-win32-x64-gnu": "4.52.4", 1528 | "@rollup/rollup-win32-x64-msvc": "4.52.4", 1529 | "fsevents": "~2.3.2" 1530 | } 1531 | }, 1532 | "node_modules/rollup-plugin-typescript2": { 1533 | "version": "0.36.0", 1534 | "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.36.0.tgz", 1535 | "integrity": "sha512-NB2CSQDxSe9+Oe2ahZbf+B4bh7pHwjV5L+RSYpCu7Q5ROuN94F9b6ioWwKfz3ueL3KTtmX4o2MUH2cgHDIEUsw==", 1536 | "dev": true, 1537 | "license": "MIT", 1538 | "dependencies": { 1539 | "@rollup/pluginutils": "^4.1.2", 1540 | "find-cache-dir": "^3.3.2", 1541 | "fs-extra": "^10.0.0", 1542 | "semver": "^7.5.4", 1543 | "tslib": "^2.6.2" 1544 | }, 1545 | "peerDependencies": { 1546 | "rollup": ">=1.26.3", 1547 | "typescript": ">=2.4.0" 1548 | } 1549 | }, 1550 | "node_modules/scheduler": { 1551 | "version": "0.23.2", 1552 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", 1553 | "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", 1554 | "dev": true, 1555 | "license": "MIT", 1556 | "peer": true, 1557 | "dependencies": { 1558 | "loose-envify": "^1.1.0" 1559 | } 1560 | }, 1561 | "node_modules/semver": { 1562 | "version": "7.7.2", 1563 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", 1564 | "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", 1565 | "dev": true, 1566 | "license": "ISC", 1567 | "bin": { 1568 | "semver": "bin/semver.js" 1569 | }, 1570 | "engines": { 1571 | "node": ">=10" 1572 | } 1573 | }, 1574 | "node_modules/sharp": { 1575 | "version": "0.34.3", 1576 | "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz", 1577 | "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==", 1578 | "dev": true, 1579 | "hasInstallScript": true, 1580 | "license": "Apache-2.0", 1581 | "optional": true, 1582 | "dependencies": { 1583 | "color": "^4.2.3", 1584 | "detect-libc": "^2.0.4", 1585 | "semver": "^7.7.2" 1586 | }, 1587 | "engines": { 1588 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1589 | }, 1590 | "funding": { 1591 | "url": "https://opencollective.com/libvips" 1592 | }, 1593 | "optionalDependencies": { 1594 | "@img/sharp-darwin-arm64": "0.34.3", 1595 | "@img/sharp-darwin-x64": "0.34.3", 1596 | "@img/sharp-libvips-darwin-arm64": "1.2.0", 1597 | "@img/sharp-libvips-darwin-x64": "1.2.0", 1598 | "@img/sharp-libvips-linux-arm": "1.2.0", 1599 | "@img/sharp-libvips-linux-arm64": "1.2.0", 1600 | "@img/sharp-libvips-linux-ppc64": "1.2.0", 1601 | "@img/sharp-libvips-linux-s390x": "1.2.0", 1602 | "@img/sharp-libvips-linux-x64": "1.2.0", 1603 | "@img/sharp-libvips-linuxmusl-arm64": "1.2.0", 1604 | "@img/sharp-libvips-linuxmusl-x64": "1.2.0", 1605 | "@img/sharp-linux-arm": "0.34.3", 1606 | "@img/sharp-linux-arm64": "0.34.3", 1607 | "@img/sharp-linux-ppc64": "0.34.3", 1608 | "@img/sharp-linux-s390x": "0.34.3", 1609 | "@img/sharp-linux-x64": "0.34.3", 1610 | "@img/sharp-linuxmusl-arm64": "0.34.3", 1611 | "@img/sharp-linuxmusl-x64": "0.34.3", 1612 | "@img/sharp-wasm32": "0.34.3", 1613 | "@img/sharp-win32-arm64": "0.34.3", 1614 | "@img/sharp-win32-ia32": "0.34.3", 1615 | "@img/sharp-win32-x64": "0.34.3" 1616 | } 1617 | }, 1618 | "node_modules/simple-swizzle": { 1619 | "version": "0.2.2", 1620 | "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", 1621 | "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", 1622 | "dev": true, 1623 | "license": "MIT", 1624 | "optional": true, 1625 | "dependencies": { 1626 | "is-arrayish": "^0.3.1" 1627 | } 1628 | }, 1629 | "node_modules/source-map-js": { 1630 | "version": "1.2.1", 1631 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1632 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1633 | "dev": true, 1634 | "license": "BSD-3-Clause", 1635 | "engines": { 1636 | "node": ">=0.10.0" 1637 | } 1638 | }, 1639 | "node_modules/styled-jsx": { 1640 | "version": "5.1.6", 1641 | "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", 1642 | "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", 1643 | "dev": true, 1644 | "license": "MIT", 1645 | "dependencies": { 1646 | "client-only": "0.0.1" 1647 | }, 1648 | "engines": { 1649 | "node": ">= 12.0.0" 1650 | }, 1651 | "peerDependencies": { 1652 | "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" 1653 | }, 1654 | "peerDependenciesMeta": { 1655 | "@babel/core": { 1656 | "optional": true 1657 | }, 1658 | "babel-plugin-macros": { 1659 | "optional": true 1660 | } 1661 | } 1662 | }, 1663 | "node_modules/tslib": { 1664 | "version": "2.8.1", 1665 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 1666 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 1667 | "dev": true 1668 | }, 1669 | "node_modules/typescript": { 1670 | "version": "5.9.3", 1671 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 1672 | "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 1673 | "dev": true, 1674 | "license": "Apache-2.0", 1675 | "bin": { 1676 | "tsc": "bin/tsc", 1677 | "tsserver": "bin/tsserver" 1678 | }, 1679 | "engines": { 1680 | "node": ">=14.17" 1681 | } 1682 | }, 1683 | "node_modules/undici-types": { 1684 | "version": "6.19.8", 1685 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 1686 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 1687 | "dev": true, 1688 | "license": "MIT" 1689 | }, 1690 | "node_modules/universalify": { 1691 | "version": "2.0.1", 1692 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", 1693 | "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", 1694 | "dev": true, 1695 | "license": "MIT", 1696 | "engines": { 1697 | "node": ">= 10.0.0" 1698 | } 1699 | } 1700 | } 1701 | } 1702 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bitpatty/next-image-s3-imgproxy-loader", 3 | "version": "0.13.1", 4 | "description": "imgproxy S3 extension for next/image", 5 | "author": "Matteias Collet ", 6 | "homepage": "https://github.com/BitPatty/next-image-s3-imgproxy-loader#readme", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/BitPatty/next-image-s3-imgproxy-loader.git" 10 | }, 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/BitPatty/next-image-s3-imgproxy-loader/issues" 14 | }, 15 | "files": [ 16 | "dist" 17 | ], 18 | "contributors": [ 19 | { 20 | "name": "Kevin Mahoney", 21 | "email": "kevin@icecube.dog", 22 | "url": "https://github.com/kevcube" 23 | } 24 | ], 25 | "main": "dist/cjs/index.jsx", 26 | "module": "dist/esm/index.jsx", 27 | "types": "dist/types/index.d.ts", 28 | "exports": { 29 | ".": { 30 | "import": "./dist/esm/index.jsx", 31 | "require": "./dist/cjs/index.jsx", 32 | "types": "./dist/types/index.d.ts" 33 | } 34 | }, 35 | "scripts": { 36 | "build": "rm -rf dist && rollup -c", 37 | "deps:force-upgrade": "npm-check-updates -u && rm -rf node_modules && rm -f package-lock.json && npm i" 38 | }, 39 | "devDependencies": { 40 | "@types/node": "22.8.1", 41 | "@types/react": "19.2.2", 42 | "next": "15.5.6", 43 | "npm-check-updates": "19.1.1", 44 | "prettier": "3.6.2", 45 | "rollup": "4.52.4", 46 | "rollup-plugin-typescript2": "0.36.0", 47 | "tslib": "2.8.1", 48 | "typescript": "5.9.3" 49 | }, 50 | "peerDependencies": { 51 | "next": ">=13.0.0 <16.0.0" 52 | }, 53 | "dependencies": { 54 | "@bitpatty/imgproxy-url-builder": "^2.0.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /request_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitPatty/next-image-s3-imgproxy-loader/a4787993d28d3fafd883a36fbe7242160945229c/request_flow.png -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import pkg from './package.json' with { type: 'json' }; 2 | 3 | import fs from 'fs'; 4 | import path, { dirname } from 'path'; 5 | import { fileURLToPath } from 'url'; 6 | 7 | import typescript from 'rollup-plugin-typescript2'; 8 | 9 | const __dirname = dirname(fileURLToPath(import.meta.url)); 10 | 11 | const transformers = [ 12 | () => ({ 13 | before: [], 14 | after: [], 15 | }), 16 | ]; 17 | 18 | const createPackageJson = { 19 | writeBundle: (opts) => { 20 | if (!['es', 'cjs'].includes(opts.format) || opts.file === pkg.types) return; 21 | const dirName = path.join(__dirname, path.dirname(opts.file)); 22 | const output = JSON.stringify({ 23 | type: opts.format === 'es' ? 'module' : 'commonjs', 24 | }); 25 | fs.writeFileSync(path.join(dirName, 'package.json'), output); 26 | }, 27 | }; 28 | 29 | export default [ 30 | { 31 | input: 'src/index.tsx', 32 | output: [ 33 | { 34 | file: pkg.main, 35 | format: 'cjs', 36 | exports: 'named', 37 | sourcemap: true, 38 | strict: true, 39 | compact: false, 40 | }, 41 | { 42 | file: pkg.module, 43 | format: 'es', 44 | exports: 'named', 45 | sourcemap: true, 46 | strict: true, 47 | compact: false, 48 | }, 49 | ], 50 | plugins: [ 51 | typescript({ 52 | tsconfig: 'tsconfig.build.json', 53 | useTsconfigDeclarationDir: true, 54 | transformers, 55 | tsconfigOverride: { 56 | compilerOptions: { 57 | declaration: true, 58 | declarationDir: dirname(pkg.types), 59 | }, 60 | }, 61 | }), 62 | createPackageJson, 63 | ], 64 | external: [ 65 | 'react', 66 | 'next/image', 67 | 'https', 68 | 'http', 69 | '@bitpatty/imgproxy-url-builder', 70 | ], 71 | }, 72 | ]; 73 | -------------------------------------------------------------------------------- /scripts/minio-dump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | SCRIPT_PATH=$(dirname "$0") 4 | echo "Dumping minio files" 5 | cd $SCRIPT_PATH/.. 6 | minio-cli cp --recursive myminio ./.devcontainer/minio-dump -------------------------------------------------------------------------------- /scripts/minio-restore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | SCRIPT_PATH=$(dirname "$0") 4 | echo "Restoring minio files" 5 | cd $SCRIPT_PATH/../.devcontainer/minio-dump/test-bucket 6 | echo $PWD 7 | minio-cli mb --ignore-existing myminio/test-bucket 8 | minio-cli rm --recursive --force myminio/test-bucket || true 9 | minio-cli cp --recursive . myminio -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Image, { ImageLoaderProps, ImageProps } from 'next/image'; 3 | 4 | import { ParsedUrlQuery } from 'node:querystring'; 5 | import { request as httpsRequest } from 'https'; 6 | import { 7 | ServerResponse, 8 | request as httpRequest, 9 | OutgoingHttpHeaders, 10 | } from 'http'; 11 | 12 | import { ParamBuilder } from '@bitpatty/imgproxy-url-builder'; 13 | 14 | import Logger, { LoggerOptions } from './logger'; 15 | 16 | /** 17 | * The options for the request handler 18 | */ 19 | type HandlerOptions = { 20 | /** 21 | * The secrets for signing the URLs 22 | */ 23 | signature?: { 24 | /** 25 | * The hex-encoded imgproxy key (IMGPROXY_KEY) 26 | */ 27 | key: string; 28 | 29 | /** 30 | * The hex-encoded imgproxy salt (IMGPROXY_SALT) 31 | */ 32 | salt: string; 33 | }; 34 | 35 | /** 36 | * The auth token (IMGPROXY_SECRET), will be included in the Authorization request 37 | * header as bearer token 38 | */ 39 | authToken?: string; 40 | 41 | /** 42 | * Restrict access to the specified buckets 43 | */ 44 | bucketWhitelist?: string[]; 45 | 46 | /** 47 | * The imgproxy headers that should be forwarded to the client. Overrides the default 48 | * set of response headers. 49 | * 50 | * If specified, all headers not included in this configuration will be stripped 51 | * from the response. 52 | */ 53 | forwardedHeaders?: string[]; 54 | 55 | /** 56 | * Additional request headers that should be sent to imgproxy 57 | */ 58 | requestHeaders?: OutgoingHttpHeaders; 59 | 60 | /** 61 | * The logger configuration 62 | */ 63 | logging?: LoggerOptions; 64 | }; 65 | 66 | /** 67 | * The default NextJS endpoint that should be used for proxying the images 68 | */ 69 | const IMGPROXY_ENDPOINT = '/_next/imgproxy'; 70 | 71 | /** 72 | * The regex for validating whether a source string is valid (/) 73 | */ 74 | const SRC_REGEX = /^[^/.]+\/.+[^/]$/; 75 | 76 | /** 77 | * The default imgproxy response headers that should be forwarded to the client. 78 | */ 79 | const FORWARDED_HEADERS = [ 80 | 'date', 81 | 'expires', 82 | 'content-type', 83 | 'content-length', 84 | 'cache-control', 85 | 'content-disposition', 86 | 'content-dpr', 87 | ]; 88 | 89 | /** 90 | * Builds the final request path to retrieve an image from the imgproxy instance 91 | * 92 | * @param src The source file 93 | * @param options The imgproxy options 94 | * @returns The imgproxy request path 95 | */ 96 | const buildRequestPath = ( 97 | src: string, 98 | options?: { 99 | imgproxyParams?: string; 100 | signature?: { 101 | key: string; 102 | salt: string; 103 | }; 104 | }, 105 | ): string => { 106 | const { imgproxyParams, signature } = options ?? {}; 107 | 108 | const paramBuilder = new ParamBuilder(); 109 | if (imgproxyParams && imgproxyParams.includes(':')) { 110 | // @ts-ignore 111 | paramBuilder.modifiers.set('params', imgproxyParams); 112 | } 113 | 114 | return paramBuilder.build({ 115 | path: `s3://${src}`, 116 | signature, 117 | }); 118 | }; 119 | 120 | /** 121 | * Handles an image request 122 | * 123 | * @param imgproxyBaseUrl The URL to the imgproxy instance 124 | * @param query The URL query generated by the {@link ImgProxyParamBuilder} 125 | * @param res The response to which the resulting file should be piped to 126 | * @param options Optional configuration options for the imgproxy connection 127 | */ 128 | const handle = ( 129 | imgproxyBaseUrl: URL, 130 | query: ParsedUrlQuery, 131 | res: ServerResponse, 132 | options?: HandlerOptions, 133 | ): void => { 134 | const { src, params } = query; 135 | Logger.debug(options?.logging, 'Processing query', { src, params }); 136 | 137 | const { 138 | authToken, 139 | bucketWhitelist, 140 | requestHeaders, 141 | forwardedHeaders, 142 | signature, 143 | } = options ?? {}; 144 | 145 | // If the source is not set or fails the regex check throw a 400 146 | if (!src || Array.isArray(src) || !SRC_REGEX.test(src)) { 147 | Logger.error(options?.logging, 'Source failed validation check', src); 148 | res.statusCode = 400; 149 | res.end(); 150 | return; 151 | } 152 | 153 | // If the bucket whitelist is set throw a 400 in case the bucket is 154 | // not included 155 | const bucketName = src.split('/')[0]; 156 | if (bucketWhitelist && !bucketWhitelist.includes(bucketName)) { 157 | Logger.error( 158 | options?.logging, 159 | 'Requested bucket is not whitelisted', 160 | bucketName, 161 | ); 162 | res.statusCode = 400; 163 | res.end(); 164 | return; 165 | } 166 | 167 | const requestPath = buildRequestPath(src, { 168 | imgproxyParams: params as string, 169 | signature, 170 | }); 171 | 172 | Logger.debug(options?.logging, 'Built imgproxy URL', requestPath); 173 | 174 | const reqProto = imgproxyBaseUrl.protocol.startsWith('https') 175 | ? httpsRequest 176 | : httpRequest; 177 | 178 | const req = reqProto( 179 | { 180 | hostname: imgproxyBaseUrl.hostname, 181 | ...(imgproxyBaseUrl.port ? { port: imgproxyBaseUrl.port } : {}), 182 | path: requestPath, 183 | method: 'GET', 184 | headers: { 185 | ...(authToken 186 | ? { Authorization: `Bearer ${authToken}`, ...requestHeaders } 187 | : requestHeaders), 188 | }, 189 | }, 190 | (r) => { 191 | (forwardedHeaders ?? FORWARDED_HEADERS).forEach((h) => { 192 | if (r.headers[h]) { 193 | Logger.debug(options?.logging, 'Forwarding header', { 194 | requestPath, 195 | header: h, 196 | }); 197 | res.setHeader(h, r.headers[h] as string); 198 | } 199 | }); 200 | 201 | if (r.statusCode) { 202 | Logger.debug(options?.logging, 'Received status code', { 203 | requestPath, 204 | statusCode: r.statusCode, 205 | }); 206 | res.statusCode = r.statusCode; 207 | } 208 | 209 | r.pipe(res); 210 | r.on('end', () => { 211 | Logger.debug(options?.logging, 'Stream ended', { requestPath }); 212 | res.end(); 213 | }); 214 | }, 215 | ); 216 | 217 | req.on('error', (e) => { 218 | Logger.error(options?.logging, 'Stream error', { requestPath, error: e }); 219 | res.statusCode = 500; 220 | res.end(); 221 | req.destroy(); 222 | }); 223 | 224 | req.end(); 225 | }; 226 | 227 | /** 228 | * The props for the component 229 | */ 230 | type ProxyImageProps = { 231 | /** 232 | * The file path in the format `/` 233 | */ 234 | file: string; 235 | 236 | /** 237 | * The imgproxy params 238 | */ 239 | proxyParams?: string; 240 | 241 | /** 242 | * The NextJS endpoint handling the proxy request 243 | */ 244 | endpoint?: string; 245 | }; 246 | 247 | const buildProxyImagePath = ( 248 | file: string, 249 | options?: Omit, 250 | ): string => { 251 | const { proxyParams, endpoint } = options ?? {}; 252 | const urlParams = new URLSearchParams(); 253 | 254 | urlParams.append('src', file); 255 | if (proxyParams) urlParams.append('params', proxyParams); 256 | 257 | return `${endpoint ?? IMGPROXY_ENDPOINT}?${urlParams.toString()}`; 258 | }; 259 | 260 | const ProxyImage = ({ 261 | file, 262 | proxyParams, 263 | endpoint, 264 | ...props 265 | }: ProxyImageProps & 266 | Omit) => { 267 | const imageLoader = ({ src, width }: ImageLoaderProps): string => { 268 | const urlParams = new URLSearchParams(); 269 | urlParams.append('src', src); 270 | if (proxyParams) urlParams.append('params', proxyParams); 271 | 272 | // This doesn't actually do anything, it's just to suppress 273 | // this error https://nextjs.org/docs/messages/next-image-missing-loader-width 274 | if (width) urlParams.append('width', width.toString()); 275 | 276 | // will return /_next/imgproxy?src=...¶ms=...&width=... 277 | return `${endpoint ?? IMGPROXY_ENDPOINT}?${urlParams.toString()}`; 278 | }; 279 | 280 | // Toggle fill on if width is not provided 281 | const fillProp = props.fill 282 | ? { fill: true } 283 | : props.width == null && props.fill == null 284 | ? { fill: true } 285 | : {}; 286 | 287 | // Suppress missing sizes warning if fill is set 288 | // as it doesn't matter for this image optimization 289 | // 290 | // See https://nextjs.org/docs/pages/api-reference/components/image#sizes 291 | const sizesProp = 292 | props.sizes != null 293 | ? { sizes: props.sizes } 294 | : fillProp.fill 295 | ? { sizes: '100%' } 296 | : {}; 297 | 298 | return ( 299 | 306 | ); 307 | }; 308 | 309 | export { ProxyImage, IMGPROXY_ENDPOINT, buildProxyImagePath, handle }; 310 | export type { HandlerOptions, ProxyImageProps, LoggerOptions }; 311 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | enum LOG_LEVEL { 2 | debug = 2, 3 | warn = 4, 4 | error = 5, 5 | } 6 | 7 | type LoggerOptions = { 8 | logger?: Pick; 9 | level?: keyof typeof LOG_LEVEL; 10 | }; 11 | 12 | type LogFunc = ( 13 | opts: LoggerOptions | undefined, 14 | msg: string, 15 | ...args: unknown[] 16 | ) => void; 17 | 18 | class Logger { 19 | static #shouldLog(opts: LoggerOptions | undefined, lvl: LOG_LEVEL): boolean { 20 | return LOG_LEVEL[opts?.level ?? 'error'] <= lvl; 21 | } 22 | 23 | static #log( 24 | lvl: keyof typeof LOG_LEVEL, 25 | opts: LoggerOptions | undefined, 26 | msg: string, 27 | ...args: unknown[] 28 | ) { 29 | if (!this.#shouldLog(opts, LOG_LEVEL[lvl])) return; 30 | (opts?.logger ?? console)[lvl](msg, ...args); 31 | } 32 | 33 | public static readonly warn: LogFunc = (...args) => 34 | this.#log('warn', ...args); 35 | 36 | public static readonly debug: LogFunc = (...args) => 37 | this.#log('debug', ...args); 38 | 39 | public static readonly error: LogFunc = (...args) => 40 | this.#log('error', ...args); 41 | } 42 | 43 | export default Logger; 44 | export { LoggerOptions }; 45 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "esnext", 5 | "target": "es6", 6 | "lib": ["es6", "dom", "es2016", "es2017"], 7 | "sourceMap": true, 8 | "allowJs": false, 9 | "jsx": "react", 10 | "declaration": true, 11 | "emitDeclarationOnly": false, 12 | "moduleResolution": "node", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true 20 | }, 21 | "include": ["src"], 22 | "exclude": ["node_modules", "dist", "example", "rollup.config.js"] 23 | } 24 | --------------------------------------------------------------------------------