媒体库:签名直传、真实文件类型校验、去除 EXIF/GPS、替代文本、焦点和 srcset
安装
genpm add @core/media你将获得
- 源代码位于 src/lib/media/,共 13 个文件。 (32.5 kB)
- AI 规则位于 src/lib/media/AGENTS.md,另附 IDE 规则文件。
- 添加到 .env.example 的环境变量:MEDIA_TRANSFORM。
- 自动为你解析 @core/contracts, @core/db, @core/storage。
README
此包没有 README。
这正是你的 AI 在 src/lib/media 中工作时读取的内容。不会向其上下文添加其他任何内容。
@core/media — rules for AI agents
Purpose
Media library on top of @core/storage: the browser uploads directly with a signed URL to a private staging key
(uploads/pending/<id>.<ext>), then confirmUpload checks the real file type by its bytes (declared type must match),
removes EXIF/GPS/XMP from JPEG, PNG and WebP without re-encoding, writes the validated bytes to the final public/
key, deletes the staging copy and stores dimensions. Malformed images (chunk lengths past the end) are rejected. Alt text and focal point per file, responsive URLs (Cloudflare Images or Next.js
image optimizer) and ready <img> attributes. SVG and HTML are never accepted. Table: media.
Map
index.ts— public API:createUpload,confirmUpload,getMedia,updateMedia,deleteMedia,imageUrl,imgAttrs,mediaAdminResource.remote.ts—importRemoteImage(url, { allowedHosts })for server-side imports (https, host allowlist, size limit).library.ts— upload flow, allowed types andstagingKey(media).sniff.ts,strip.ts— byte checks.urls.ts— URLs and<img>attributes.adapters/hono.ts—mediaRoutes({ authorize }).adapters/next.ts—createUploadRoute,confirmUploadRoute.
Integration
- Configure @core/storage first (bucket CORS must allow
PUTfrom the site). Recommended: a bucket lifecycle rule that deletesuploads/pending/objects after 1 day (abandoned or replayed uploads). OptionalMEDIA_TRANSFORM:cloudflare(Images transformations enabled on the zone),vercel(Next.js image optimizer; add the storage host toimages.remotePatterns) ornone. - Migrations as in
src/lib/db/AGENTS.md. - Mount the upload routes with an
authorize(req)that returns{ userId }only for users withmedia:create(@core/rbac). - Upload from the browser:
POST /api/media/uploadswith{ filename, mime, size }→PUTthe file toupload.urlwithupload.headers→POST /api/media/uploads/<id>/confirm. Store the mediaidin your content. - Render:
const m = await getMedia(id); <img {...imgAttrs(m, { sizes: '(min-width: 768px) 50vw, 100vw' })} />. - Add
mediaAdminResourcetosrc/genpm/admin.ts. - Verify: upload a photo with GPS data; after confirm, the stored file has no EXIF and the row has width/height.
Conventions
- Reference media by
idin content; resolve URLs at render time (driver or CDN can change). - Every non-decorative image needs
alt; the admin can filtermissingAlt=true. - Hero/LCP images:
imgAttrs(m, { priority: true }); everything else stays lazy.
Don't
- Don't accept files without
confirmUpload; pending or rejected media must never be shown. - Don't presign uploads to the final
public/key: a signed PUT can be replayed until it expires and would swap a validated file for an unchecked one (servedimmutable). Upload tostagingKey(); onlyconfirmUploadwritespublic/. - Don't allow SVG uploads or serve user files from the site's own origin without the storage route's sandbox headers.
- Don't hotlink third-party images; copy them into storage first.
# @core/media — rules for AI agents
## Purpose
Media library on top of @core/storage: the browser uploads directly with a signed URL to a private staging key
(`uploads/pending/<id>.<ext>`), then `confirmUpload` checks the real file type by its bytes (declared type must match),
removes EXIF/GPS/XMP from JPEG, PNG and WebP without re-encoding, writes the validated bytes to the final `public/`
key, deletes the staging copy and stores dimensions. Malformed images (chunk lengths past the end) are rejected. Alt text and focal point per file, responsive URLs (Cloudflare Images or Next.js
image optimizer) and ready `<img>` attributes. SVG and HTML are never accepted. Table: `media`.
## Map
- `index.ts` — public API: `createUpload`, `confirmUpload`, `getMedia`, `updateMedia`, `deleteMedia`, `imageUrl`, `imgAttrs`, `mediaAdminResource`.
- `remote.ts` — `importRemoteImage(url, { allowedHosts })` for server-side imports (https, host allowlist, size limit).
- `library.ts` — upload flow, allowed types and `stagingKey(media)`. `sniff.ts`, `strip.ts` — byte checks. `urls.ts` — URLs and `<img>` attributes.
- `adapters/hono.ts` — `mediaRoutes({ authorize })`. `adapters/next.ts` — `createUploadRoute`, `confirmUploadRoute`.
## Integration
1. Configure @core/storage first (bucket CORS must allow `PUT` from the site). Recommended: a bucket lifecycle rule
that deletes `uploads/pending/` objects after 1 day (abandoned or replayed uploads). Optional `MEDIA_TRANSFORM`:
`cloudflare` (Images transformations enabled on the zone), `vercel` (Next.js image optimizer; add the storage host to `images.remotePatterns`) or `none`.
2. Migrations as in `src/lib/db/AGENTS.md`.
3. Mount the upload routes with an `authorize(req)` that returns `{ userId }` only for users with `media:create` (@core/rbac).
4. Upload from the browser: `POST /api/media/uploads` with `{ filename, mime, size }` → `PUT` the file to `upload.url`
with `upload.headers` → `POST /api/media/uploads/<id>/confirm`. Store the media `id` in your content.
5. Render: `const m = await getMedia(id); <img {...imgAttrs(m, { sizes: '(min-width: 768px) 50vw, 100vw' })} />`.
6. Add `mediaAdminResource` to `src/genpm/admin.ts`.
7. Verify: upload a photo with GPS data; after confirm, the stored file has no EXIF and the row has width/height.
## Conventions
- Reference media by `id` in content; resolve URLs at render time (driver or CDN can change).
- Every non-decorative image needs `alt`; the admin can filter `missingAlt=true`.
- Hero/LCP images: `imgAttrs(m, { priority: true })`; everything else stays lazy.
## Don't
- Don't accept files without `confirmUpload`; pending or rejected media must never be shown.
- Don't presign uploads to the final `public/` key: a signed PUT can be replayed until it expires and would swap a
validated file for an unchecked one (served `immutable`). Upload to `stagingKey()`; only `confirmUpload` writes `public/`.
- Don't allow SVG uploads or serve user files from the site's own origin without the storage route's sandbox headers.
- Don't hotlink third-party images; copy them into storage first.
应用 .genpmignore 后将被注入的确切目录树。固定于
// Adaptador Next.js:
// app/api/media/uploads/route.ts → export const POST = createUploadRoute(authorize);
// app/api/media/uploads/[id]/confirm/route.ts → export const POST = confirmUploadRoute(authorize);
import { type Authorize, handleConfirmUpload, handleCreateUpload } from './http.ts';
export const createUploadRoute = (authorize: Authorize) => (req: Request) => handleCreateUpload(req, authorize);
export const confirmUploadRoute = (authorize: Authorize) => async (req: Request, ctx: { params: Promise<{ id: string }> }) =>
handleConfirmUpload(req, (await ctx.params).id, authorize);
此包未声明 MCP 服务器。
| 版本 | 提交 | 发布时间 | 扫描 |
|---|---|---|---|
| 1.1.0 | 868490c | 4小时前 | 扫描通过 |
- npm
- zod ^4.0.0
- 建议
- GenPM 会给出 npm 命令建议,只有你同意时才会运行。
- 扫描
- 扫描通过 · 0 个问题
- 提交
- v1.1.0 → 868490cea569361668f1903822e20c1becc618d4 · 获取后已校验
- 脚本
- 无。GenPM 从不运行包中的代码。
- 许可证
- MIT
- 质量
- 100/100
- 可识别的许可证已满足
- AGENTS.md 说明了用途已满足
- AGENTS.md 包含集成步骤已满足
- AGENTS.md 列出约定或禁止事项已满足
- 包含测试已满足
- 通过安全扫描已满足
- 最近 6 个月内发布已满足
- 已验证的发布者已满足
- 摘要和关键词已满足
- 举报
- 发现问题了吗?