{"_id":"@andrey.tsirkun/strapi-plugin-watermark","_rev":"3-0ca7aebf67a81d3b049967f8b21f7fbf","name":"@andrey.tsirkun/strapi-plugin-watermark","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"@andrey.tsirkun/strapi-plugin-watermark","version":"1.0.0","keywords":["strapi","strapi-plugin","strapi5","watermark","image","sharp","media-library","upload","cms"],"author":{"name":"Andrey-Tsirkun","email":"andrey.tsirkun@gmail.com"},"license":"MIT","_id":"@andrey.tsirkun/strapi-plugin-watermark@1.0.0","maintainers":[{"name":"andrey.tsirkun","email":"andrey.tsirkun@gmail.com"}],"homepage":"https://github.com/Andrey-Tsirkun/strapi-watermark#readme","bugs":{"url":"https://github.com/Andrey-Tsirkun/strapi-watermark/issues"},"dist":{"shasum":"5fc96ab1144aa3b4034aee7e756ee923a79114e1","tarball":"https://registry.npmjs.org/@andrey.tsirkun/strapi-plugin-watermark/-/strapi-plugin-watermark-1.0.0.tgz","fileCount":23,"integrity":"sha512-8AVvcCKPa9014tYmnqRwoMm4GErTi5Byj8nrT050l5eRLhLg5VdZl31JxYo0BYCqRl0VxrJ4NylOrUrWSUlCiQ==","signatures":[{"sig":"MEQCIHX3t5lw5y2/WMr50th431Lr85WxgGExyOIaEbJZ3aGiAiA+sp6ITgnKC7XlGy4ON0v/PO2ko09mFI3a2GI8iATRAQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":307387},"type":"commonjs","strapi":{"kind":"plugin","name":"watermark","description":"Apply watermarks to uploaded media","displayName":"Watermark"},"engines":{"npm":">=10.0.0","node":">=20.0.0 <=22.x.x"},"exports":{"./package.json":"./package.json","./strapi-admin":{"types":"./dist/admin/src/index.d.ts","import":"./dist/admin/index.mjs","source":"./admin/src/index.ts","default":"./dist/admin/index.js","require":"./dist/admin/index.js"},"./strapi-server":{"types":"./dist/server/src/index.d.ts","import":"./dist/server/index.mjs","source":"./server/src/index.ts","default":"./dist/server/index.js","require":"./dist/server/index.js"}},"gitHead":"927f88c275930fd0b53ba1abd70a398b5c087c7c","scripts":{"test":"jest --config jest.unit.config.js --runInBand","build":"strapi-plugin build","watch":"strapi-plugin watch","verify":"strapi-plugin verify","test:ts":"npm run test:ts:front && npm run test:ts:back","test:unit":"jest --config jest.unit.config.js --runInBand","test:watch":"jest --config jest.unit.config.js --runInBand --watch","watch:link":"strapi-plugin watch:link","test:ts:back":"tsc -p server/tsconfig.json","test:ts:front":"tsc -p admin/tsconfig.json","test:integration":"cross-env NODE_ENV=test jest --config jest.integration.config.js --runInBand"},"_npmUser":{"name":"andrey.tsirkun","email":"andrey.tsirkun@gmail.com"},"repository":{"url":"git+https://github.com/Andrey-Tsirkun/strapi-watermark.git","type":"git","directory":"packages/plugin-watermark"},"_npmVersion":"10.9.2","description":"Strapi 5 plugin for applying watermarks to uploaded media","directories":{},"_nodeVersion":"22.15.0","dependencies":{"sharp":"^0.33.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","react":"^18.3.1","ts-jest":"^29.4.0","prettier":"^3.9.4","cross-env":"^7.0.3","react-dom":"^18.3.1","supertest":"^7.1.0","react-intl":"^6.8.9","typescript":"^5.9.3","@types/jest":"^29.5.14","@types/react":"^18.3.31","@strapi/icons":"^2.2.1","@strapi/strapi":"^5.50.0","@types/react-dom":"^18.3.7","@types/supertest":"^6.0.3","react-router-dom":"^6.30.4","styled-components":"^6.4.3","@strapi/sdk-plugin":"^6.1.1","@strapi/design-system":"^2.2.1","@strapi/typescript-utils":"^5.50.0","@strapi-watermark/test-utils":"*"},"peerDependencies":{"react":"^18.3.1","react-dom":"^18.3.1","react-intl":"^6.8.9","@strapi/icons":"^2.2.1","@strapi/strapi":"^5.50.0","react-router-dom":"^6.30.4","styled-components":"^6.4.3","@strapi/sdk-plugin":"^6.1.1","@strapi/design-system":"^2.2.1"},"_npmOperationalInternal":{"tmp":"tmp/strapi-plugin-watermark_1.0.0_1783698192260_0.41308784241598606","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@andrey.tsirkun/strapi-plugin-watermark","version":"1.0.1","keywords":["strapi","strapi-plugin","strapi5","watermark","image","sharp","media-library","upload","cms"],"author":{"name":"Andrey-Tsirkun","email":"andrey.tsirkun@gmail.com"},"license":"MIT","_id":"@andrey.tsirkun/strapi-plugin-watermark@1.0.1","maintainers":[{"name":"andrey.tsirkun","email":"andrey.tsirkun@gmail.com"}],"homepage":"https://github.com/Andrey-Tsirkun/strapi-watermark#readme","bugs":{"url":"https://github.com/Andrey-Tsirkun/strapi-watermark/issues"},"dist":{"shasum":"edbb2b3a6ce36cc6fcaaaf86d23e22ea4e76672b","tarball":"https://registry.npmjs.org/@andrey.tsirkun/strapi-plugin-watermark/-/strapi-plugin-watermark-1.0.1.tgz","fileCount":55,"integrity":"sha512-Rl0Hu6X3C3y+aIB3jxk3RLAKa5FHdzt70E4lb10+3hH1s45NzhabFJsrg4Bs/eE7qIdVT92Q4s9YPyPF9eunXQ==","signatures":[{"sig":"MEUCIQDrdpSVibIGQk+VZ2186n3X83cD9xldi0+pqOwjk+x9xAIgF2IGsSK+6bIvXEH4F+KuQfnW/52On1bBXZaq2syvLZY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":281588},"type":"commonjs","strapi":{"kind":"plugin","name":"watermark","description":"Apply watermarks to uploaded media","displayName":"Watermark"},"engines":{"npm":">=10.0.0","node":">=20.0.0 <=22.x.x"},"exports":{"./package.json":"./package.json","./strapi-admin":{"types":"./dist/admin/src/index.d.ts","import":"./dist/admin/index.mjs","source":"./admin/src/index.ts","default":"./dist/admin/index.js","require":"./dist/admin/index.js"},"./strapi-server":{"types":"./dist/server/src/index.d.ts","import":"./dist/server/index.mjs","source":"./server/src/index.ts","default":"./dist/server/index.js","require":"./dist/server/index.js"}},"gitHead":"e77f4cbf869a932d7216834cf843678555e15501","scripts":{"test":"jest --config jest.unit.config.js --runInBand","build":"strapi-plugin build","watch":"strapi-plugin watch","verify":"strapi-plugin verify","test:ts":"npm run test:ts:front && npm run test:ts:back","test:unit":"jest --config jest.unit.config.js --runInBand","test:watch":"jest --config jest.unit.config.js --runInBand --watch","watch:link":"strapi-plugin watch:link","test:ts:back":"tsc -p server/tsconfig.json","test:ts:front":"tsc -p admin/tsconfig.json","test:integration":"cross-env NODE_ENV=test jest --config jest.integration.config.js --runInBand"},"_npmUser":{"name":"andrey.tsirkun","email":"andrey.tsirkun@gmail.com"},"repository":{"url":"git+https://github.com/Andrey-Tsirkun/strapi-watermark.git","type":"git","directory":"packages/plugin-watermark"},"_npmVersion":"10.9.2","description":"Strapi 5 plugin for applying watermarks to uploaded media","directories":{},"_nodeVersion":"22.15.0","dependencies":{"sharp":"^0.33.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","react":"^18.3.1","ts-jest":"^29.4.0","prettier":"^3.9.4","cross-env":"^7.0.3","react-dom":"^18.3.1","supertest":"^7.1.0","react-intl":"^6.8.9","typescript":"^5.9.3","@types/jest":"^29.5.14","@types/react":"^18.3.31","@strapi/icons":"^2.2.1","@strapi/strapi":"^5.50.0","@types/react-dom":"^18.3.7","@types/supertest":"^6.0.3","react-router-dom":"^6.30.4","styled-components":"^6.4.3","@strapi/sdk-plugin":"^6.1.1","@strapi/design-system":"^2.2.1","@strapi/typescript-utils":"^5.50.0"},"peerDependencies":{"react":"^18.3.1","react-dom":"^18.3.1","react-intl":"^6.8.9","@strapi/icons":"^2.2.1","@strapi/strapi":"^5.50.0","react-router-dom":"^6.30.4","styled-components":"^6.4.3","@strapi/sdk-plugin":"^6.1.1","@strapi/design-system":"^2.2.1"},"_npmOperationalInternal":{"tmp":"tmp/strapi-plugin-watermark_1.0.1_1783772570895_0.11367008780442522","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@andrey.tsirkun/strapi-plugin-watermark","version":"1.0.2","description":"Strapi 5 plugin for applying watermarks to uploaded media","keywords":["strapi","strapi-plugin","strapi5","watermark","image","sharp","media-library","upload","cms"],"type":"commonjs","license":"MIT","author":{"name":"Andrey-Tsirkun","email":"andrey.tsirkun@gmail.com"},"repository":{"type":"git","url":"git+https://github.com/Andrey-Tsirkun/plugin-watermark.git"},"bugs":{"url":"https://github.com/Andrey-Tsirkun/plugin-watermark/issues"},"homepage":"https://github.com/Andrey-Tsirkun/plugin-watermark#readme","engines":{"node":">=20.0.0 <=22.x.x","npm":">=10.0.0"},"exports":{"./package.json":"./package.json","./strapi-admin":{"types":"./dist/admin/src/index.d.ts","source":"./admin/src/index.ts","import":"./dist/admin/index.mjs","require":"./dist/admin/index.js","default":"./dist/admin/index.js"},"./strapi-server":{"types":"./dist/server/src/index.d.ts","source":"./server/src/index.ts","import":"./dist/server/index.mjs","require":"./dist/server/index.js","default":"./dist/server/index.js"}},"scripts":{"build":"strapi-plugin build","watch":"strapi-plugin watch","watch:link":"strapi-plugin watch:link","verify":"strapi-plugin verify","test":"jest --config jest.unit.config.js --runInBand","test:watch":"jest --config jest.unit.config.js --runInBand --watch","test:unit":"jest --config jest.unit.config.js --runInBand","test:integration":"cross-env NODE_ENV=test jest --config jest.integration.config.js --runInBand","test:ts":"npm run test:ts:front && npm run test:ts:back","test:ts:front":"tsc -p admin/tsconfig.json","test:ts:back":"tsc -p server/tsconfig.json"},"dependencies":{"sharp":"^0.33.5"},"devDependencies":{"@strapi/design-system":"^2.2.1","@strapi/icons":"^2.2.1","@strapi/sdk-plugin":"^6.1.1","@strapi/strapi":"^5.50.0","@strapi/typescript-utils":"^5.50.0","@types/jest":"^29.5.14","@types/react":"^18.3.31","@types/react-dom":"^18.3.7","@types/supertest":"^6.0.3","cross-env":"^7.0.3","jest":"^29.7.0","prettier":"^3.9.4","react":"^18.3.1","react-dom":"^18.3.1","react-intl":"^6.8.9","react-router-dom":"^6.30.4","styled-components":"^6.4.3","supertest":"^7.1.0","ts-jest":"^29.4.0","typescript":"^5.9.3"},"peerDependencies":{"@strapi/design-system":"^2.2.1","@strapi/icons":"^2.2.1","@strapi/sdk-plugin":"^6.1.1","@strapi/strapi":"^5.50.0","react":"^18.3.1","react-dom":"^18.3.1","react-intl":"^6.8.9","react-router-dom":"^6.30.4","styled-components":"^6.4.3"},"publishConfig":{"access":"public"},"strapi":{"kind":"plugin","name":"watermark","displayName":"Watermark","description":"Apply watermarks to uploaded media"},"_id":"@andrey.tsirkun/strapi-plugin-watermark@1.0.2","gitHead":"98111e63eaa98c67340a45adefb1b91973a951e0","_nodeVersion":"22.15.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-Dv9qz7bSuVf3ZtlM60fnsHw/0nhQyjhjXvkbq5TQCYPcxEodJAmc8sZDZlkKutecH9zshr2Y+t25ZsrBcaBt1g==","shasum":"8336eb12ea8e3af9dadc6462ea9796d991c9efe4","tarball":"https://registry.npmjs.org/@andrey.tsirkun/strapi-plugin-watermark/-/strapi-plugin-watermark-1.0.2.tgz","fileCount":55,"unpackedSize":281661,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGHEngsvC2Z1J6RoYkD2r1AsoiHBzUqxsayMntQOs22lAiAOy9V/DwKMTjMR4UrOUc3tV83R4N8jtBlPQr/RqO11Qw=="}]},"_npmUser":{"name":"andrey.tsirkun","email":"andrey.tsirkun@gmail.com"},"directories":{},"maintainers":[{"name":"andrey.tsirkun","email":"andrey.tsirkun@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/strapi-plugin-watermark_1.0.2_1783772853408_0.7407092509187381"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-10T15:43:12.094Z","modified":"2026-07-11T12:27:33.660Z","1.0.0":"2026-07-10T15:43:12.429Z","1.0.1":"2026-07-11T12:22:51.095Z","1.0.2":"2026-07-11T12:27:33.563Z"},"bugs":{"url":"https://github.com/Andrey-Tsirkun/plugin-watermark/issues"},"author":{"name":"Andrey-Tsirkun","email":"andrey.tsirkun@gmail.com"},"license":"MIT","homepage":"https://github.com/Andrey-Tsirkun/plugin-watermark#readme","keywords":["strapi","strapi-plugin","strapi5","watermark","image","sharp","media-library","upload","cms"],"repository":{"type":"git","url":"git+https://github.com/Andrey-Tsirkun/plugin-watermark.git"},"description":"Strapi 5 plugin for applying watermarks to uploaded media","maintainers":[{"name":"andrey.tsirkun","email":"andrey.tsirkun@gmail.com"}],"readme":"# @andrey.tsirkun/strapi-plugin-watermark\n\n\n<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/Andrey-Tsirkun/plugin-watermark/main/docs/images/strapi_watermark_plugin_logo_small.png\" alt=\"Strapi Watermark Plugin\" />\n</p>\n\nProduction-ready [Strapi 5](https://strapi.io) plugin that automatically applies image watermarks to uploads in the Media Library.\n\nWorks transparently for editors: upload an image as usual, the plugin intercepts the upload pipeline, composites a watermark in memory with [Sharp](https://sharp.pixelplumbing.com/), and sends the result to your configured upload provider.\n\n## Features\n\n- Settings UI under **Settings → Watermark**\n- Watermark image stored as a Media Library relation (not a hard-coded path)\n- Configurable size, opacity, position, and margins\n- Per-format processing: original, thumbnail, small, medium, large\n- Optional `keepOriginal` mode with an extra `formats.watermarked` variant\n- MIME type and minimum-size filters\n- Provider-agnostic (local, S3, Spaces, GCS, MinIO, custom providers)\n- Graceful degradation: watermark failures never break uploads\n\n## Requirements\n\n- Strapi **5.x**\n- Node.js **20.x – 22.x**\n\n## Installation\n\nInstall from npm:\n\n```bash\nnpm install @andrey.tsirkun/strapi-plugin-watermark sharp\n```\n\nOr with yarn:\n\n```bash\nyarn add @andrey.tsirkun/strapi-plugin-watermark sharp\n```\n\nEnable the plugin in `config/plugins.ts`:\n\n```ts\nexport default {\n  watermark: {\n    enabled: true,\n    config: {\n      enabledByDefault: false,\n      defaultOpacity: 80,\n      defaultMargins: { x: 20, y: 20 },\n      defaultSize: 20,\n    },\n  },\n};\n```\n\nRebuild the admin panel and restart Strapi:\n\n```bash\nnpm run build\nnpm run develop\n```\n\n### Monorepo / local development\n\nIf you develop from source, link the package into your Strapi app and enable it the same way:\n\n```ts\nwatermark: {\n  enabled: true,\n  resolve: './packages/plugin-watermark',\n},\n```\n\n## Configuration\n\n### Plugin config (`config/plugins.ts`)\n\nThese values seed the singleton settings document on first boot:\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `enabledByDefault` | `boolean` | `false` | Initial value for the **Enabled** setting |\n| `defaultOpacity` | `number` | `80` | Initial opacity (0–100) |\n| `defaultMargins` | `{ x, y }` | `{ x: 20, y: 20 }` | Initial margin in pixels |\n| `defaultSize` | `number` | `20` | Initial watermark width as % of image width |\n\nExample:\n\n```ts\nwatermark: {\n  enabled: true,\n  config: {\n    enabledByDefault: true,\n    defaultOpacity: 70,\n    defaultMargins: { x: 16, y: 16 },\n    defaultSize: 25,\n  },\n},\n```\n\n### Admin settings (Settings → Watermark)\n\n| Setting | Description |\n|---------|-------------|\n| **Enabled** | Master switch for watermark processing |\n| **Watermark image** | PNG/JPEG from Media Library used as the watermark |\n| **Size** | Watermark width as % of the target image width |\n| **Opacity** | 0–100%, applied to the watermark alpha channel |\n| **Position** | One of 9 positions (top/center/bottom × left/center/right) |\n| **Margins** | Horizontal and vertical offset in pixels |\n| **Process formats** | Choose which generated formats receive a watermark |\n| **Keep original** | Keep the untouched original and add `formats.watermarked` |\n| **Minimum image size** | Skip when both width and height are below the threshold |\n| **Allowed MIME types** | Restrict processing to selected image types |\n\n## Screenshots\n\n| Settings page | Watermark picker |\n|---------------|------------------|\n| ![Settings page](https://raw.githubusercontent.com/Andrey-Tsirkun/plugin-watermark/main/docs/images/watermark_settings.png) | ![Watermark picker](https://raw.githubusercontent.com/Andrey-Tsirkun/plugin-watermark/main/docs/images/watermark_picker.png) |\n\n## Plugin architecture\n\n```\nAdmin UI (Settings → Watermark)\n        │\n        ▼\nAdmin API  GET/PUT /watermark/settings\n        │\n        ▼\nSettingsService  ──►  plugin::watermark.settings (singleType)\n        │\nMedia Library upload\n        │\n        ▼\nUploadInterceptorService  (wraps upload provider upload/replace)\n        │\n        ▼\nWatermarkService\n   ├── ValidationService\n   ├── ImageService\n   ├── SharpService\n   └── CoordinateService\n        │\n        ▼\nUpload provider (local / S3 / …)\n```\n\n### Server services\n\n| Service | Responsibility |\n|---------|----------------|\n| `SettingsService` | Singleton settings CRUD |\n| `ValidationService` | MIME, size, format, and enabled checks |\n| `CoordinateService` | Watermark x/y placement with margins |\n| `SharpService` | Resize, opacity, composite |\n| `ImageService` | Load watermark file from any provider |\n| `WatermarkService` | Orchestrates the processing pipeline |\n| `UploadInterceptorService` | Hooks the Strapi upload lifecycle |\n\n## How upload interception works\n\nStrapi Upload optimizes images and calls the upload **provider** (`upload` / `replace`) for each format (original, thumbnail, responsive sizes). Document middleware runs too late to modify buffers before storage.\n\nThis plugin registers in `bootstrap` and wraps `plugin::upload` provider methods:\n\n1. Read the upload stream into a buffer\n2. Run `WatermarkService.applyWatermark()` when validation passes\n3. Write the processed buffer back onto the **same** upload file object\n4. Call the original provider method\n\nBecause interception happens at the provider layer, it works with any Strapi upload provider without provider-specific code.\n\n### `keepOriginal` behavior\n\n| `keepOriginal` | `processOriginal` | Result |\n|----------------|-------------------|--------|\n| `false` | `true` | Original file is replaced with the watermarked version |\n| `true` | `true` | Original stays untouched; extra `formats.watermarked` is uploaded |\n| any | `false` | Original format is not watermarked |\n\n## Supported providers\n\nAny provider compatible with Strapi 5 Upload is supported, including:\n\n- Local upload provider (default)\n- AWS S3 (`@strapi/provider-upload-aws-s3`)\n- Cloudinary\n- DigitalOcean Spaces\n- Google Cloud Storage\n- MinIO\n- Custom providers implementing `upload` / `uploadStream` and optional `getStream`\n\nThe plugin reads watermark source files via the provider `getStream` API when available, with local filesystem and HTTP fallbacks.\n\n## API\n\nAdmin routes (require authenticated admin with plugin permissions):\n\n### `GET /watermark/settings`\n\nReturns current watermark settings.\n\n**Response**\n\n```json\n{\n  \"data\": {\n    \"enabled\": true,\n    \"watermarkImage\": 1,\n    \"sizePercent\": 20,\n    \"opacity\": 80,\n    \"position\": \"bottom-right\",\n    \"marginX\": 20,\n    \"marginY\": 20,\n    \"processOriginal\": true,\n    \"processThumbnail\": false,\n    \"processSmall\": true,\n    \"processMedium\": true,\n    \"processLarge\": true,\n    \"keepOriginal\": false,\n    \"minimumImageSize\": 0,\n    \"allowedMimeTypes\": [\"image/jpeg\", \"image/png\", \"image/webp\"]\n  }\n}\n```\n\n### `PUT /watermark/settings`\n\nUpdates settings. Accepts `{ \"data\": { ... } }` or a flat body.\n\n**Permissions**\n\n- `plugin::watermark.settings.read`\n- `plugin::watermark.settings.update`\n\nSuper Admin receives these permissions on plugin registration.\n\n## Development\n\nFrom the monorepo root:\n\n```bash\n# Install dependencies\nnpm install\n\n# Build the plugin\nnpm run build:plugin\n\n# Run Strapi test app with plugin watch\nnpm run dev:all\n```\n\nOpen the admin panel, go to **Settings → Watermark**, configure the watermark image and options, then upload images via **Media Library**.\n\n### Project structure\n\n```\npackages/plugin-watermark/\n├── admin/           Admin panel (Settings page, hooks, components)\n├── server/          Plugin server (services, routes, content-types)\n├── dist/            Build output (published to npm)\n└── package.json\n```\n\n## Testing\n\n```bash\n# Full CI-style check (from monorepo root)\nnpm run verify:all\n\n# Individual steps\nnpm run test:unit --workspace=@andrey.tsirkun/strapi-plugin-watermark\nnpm run build:plugin\nnpm run test:integration --workspace=@andrey.tsirkun/strapi-plugin-watermark\nnpm run test:ts --workspace=@andrey.tsirkun/strapi-plugin-watermark\nnpm run verify --workspace=@andrey.tsirkun/strapi-plugin-watermark\n```\n\n## Publishing\n\n1. Update version in `package.json`\n2. Build the plugin:\n\n   ```bash\n   npm run build --workspace=@andrey.tsirkun/strapi-plugin-watermark\n   ```\n\n3. Verify:\n\n   ```bash\n   npm run verify --workspace=@andrey.tsirkun/strapi-plugin-watermark\n   ```\n\n4. Publish from the plugin directory:\n\n   ```bash\n   cd packages/plugin-watermark\n   npm publish --access public\n   ```\n\nPublished tarball includes `dist/`, `README.md`, and `LICENSE`.\n\n## Limitations\n\n- **Images only** — non-image uploads are passed through unchanged\n- **Sharp-supported formats** — JPEG, PNG, WebP, TIFF, AVIF (configurable via allowed MIME types)\n- **SVG / GIF** — not composited as watermark targets by default\n- **Watermark source upload** — the configured watermark file is skipped when its hash/name matches the upload\n- **In-memory processing** — very large images increase memory use during upload\n- **Animated images** — treated as static frames through Sharp\n\n## Roadmap\n\n- [ ] E2E admin tests (Playwright)\n- [ ] Batch re-watermark existing media\n- [ ] Text watermark mode (without image asset)\n- [ ] Per-folder watermark rules\n- [ ] Watermark preview in Settings UI\n\n## License\n\n[MIT](./LICENSE)\n","readmeFilename":"README.md"}