Website-Einstellungen, bearbeitbare verschachtelte Menüs und Weiterleitungen ohne Schleifen und Open Redirects
Installieren
genpm add @core/siteWas du bekommst
- Quellcode in src/lib/site/, 10 Dateien. (19,3 kB)
- KI-Regeln in src/lib/site/AGENTS.md, dazu Regeldateien für die IDE.
- Umgebungsvariablen in .env.example ergänzt: SITE_URL, SITE_REDIRECT_HOSTS.
- Löst @core/content, @core/contracts, @core/db für dich auf.
README
Dieses Paket hat keine README.
Genau das liest deine KI, wenn sie in src/lib/site arbeitet. Sonst wird ihrem Kontext nichts hinzugefügt.
@core/site — rules for AI agents
Purpose
What every site has: settings (name, logo, contact, social profiles, legal page paths) as a @core/content singleton,
editable menus up to 3 levels as a content collection (slug = location: header, footer), and redirects managed
from the admin with loop detection, safe targets (site paths or https to allowed hosts) and hit counters.
No page content, no SEO tags (@core/seo reads these settings).
Map
index.ts— public API:getSiteSettings,getMenu,upsertRedirect(byfrom),updateRedirect(by id),resolveRedirect,redirectResponse,siteAdminResources.content.ts—siteSettings,menusand their schemas.redirects.ts— redirect logic.links.ts— link validation.adapters/hono.ts—redirectMiddleware.adapters/next.ts—redirectFor(req).
Integration
- Env:
SITE_URL(https://example.com), optionalSITE_REDIRECT_HOSTS(comma-separated external hosts allowed as redirect targets). Migrations as insrc/lib/db/AGENTS.md. - Import this module once at startup (it registers the
site_settingsandmenuscontent definitions). - Seed initial values with
seedEntry(@core/content): settings and theheader/footermenus, using the site's current ones. - Render:
const settings = await getSiteSettings({ locale }),const items = await getMenu('header', { locale }). - Redirects: Hono
app.use(redirectMiddleware); Next.jsmiddleware.tswithruntime: 'nodejs'callingredirectFor(req). - Add
...siteAdminResources()tosrc/genpm/admin.ts. - Verify: create a redirect
/old → /newand request/old(301 to/new).
Conventions
- Menu links are site paths,
https://,mailto:ortel:; open external links withrel="noopener". - Redirect sources are exact paths without query; the incoming query string is preserved.
- Permissions:
site_settings:*,menus:*,redirects:read|create|update|delete.
Don't
- Don't hardcode the site name, logo or menus in components once this module is installed.
- Don't build redirects from user input; only from the admin through
upsertRedirect/updateRedirect. Editing changes that row by id and fails withinvalid_path(422) if the newfrombelongs to another redirect. - Don't put legal text here; only the paths of the legal pages.
# @core/site — rules for AI agents
## Purpose
What every site has: settings (name, logo, contact, social profiles, legal page paths) as a @core/content singleton,
editable menus up to 3 levels as a content collection (slug = location: `header`, `footer`), and redirects managed
from the admin with loop detection, safe targets (site paths or https to allowed hosts) and hit counters.
No page content, no SEO tags (@core/seo reads these settings).
## Map
- `index.ts` — public API: `getSiteSettings`, `getMenu`, `upsertRedirect` (by `from`), `updateRedirect` (by id), `resolveRedirect`, `redirectResponse`, `siteAdminResources`.
- `content.ts` — `siteSettings`, `menus` and their schemas. `redirects.ts` — redirect logic. `links.ts` — link validation.
- `adapters/hono.ts` — `redirectMiddleware`. `adapters/next.ts` — `redirectFor(req)`.
## Integration
1. Env: `SITE_URL` (`https://example.com`), optional `SITE_REDIRECT_HOSTS` (comma-separated external hosts allowed as redirect targets). Migrations as in `src/lib/db/AGENTS.md`.
2. Import this module once at startup (it registers the `site_settings` and `menus` content definitions).
3. Seed initial values with `seedEntry` (@core/content): settings and the `header`/`footer` menus, using the site's current ones.
4. Render: `const settings = await getSiteSettings({ locale })`, `const items = await getMenu('header', { locale })`.
5. Redirects: Hono `app.use(redirectMiddleware)`; Next.js `middleware.ts` with `runtime: 'nodejs'` calling `redirectFor(req)`.
6. Add `...siteAdminResources()` to `src/genpm/admin.ts`.
7. Verify: create a redirect `/old → /new` and request `/old` (301 to `/new`).
## Conventions
- Menu links are site paths, `https://`, `mailto:` or `tel:`; open external links with `rel="noopener"`.
- Redirect sources are exact paths without query; the incoming query string is preserved.
- Permissions: `site_settings:*`, `menus:*`, `redirects:read|create|update|delete`.
## Don't
- Don't hardcode the site name, logo or menus in components once this module is installed.
- Don't build redirects from user input; only from the admin through `upsertRedirect` / `updateRedirect`. Editing changes that row by id and fails with `invalid_path` (422) if the new `from` belongs to another redirect.
- Don't put legal text here; only the paths of the legal pages.
Der genaue Baum, der nach .genpmignore eingebunden wird. Gepinnt an
// Recursos de panel: ajustes y menús (vía @core/content) y redirecciones.
import { asc, count, eq, ilike, or } from 'drizzle-orm';
import { z } from 'zod';
import { contentAdminResource } from '../content/index.ts';
import type { AdminContext, AdminResource } from '../contracts/index.ts';
import { getDb } from '../db/index.ts';
import { SiteError } from './links.ts';
import { deleteRedirect, REDIRECT_STATUSES, updateRedirect, upsertRedirect } from './redirects.ts';
import { type Redirect, redirects } from './schema.ts';
const RedirectInput = z.object({
from: z.string(),
to: z.string(),
status: z.coerce.number().pipe(z.union(REDIRECT_STATUSES.map((s) => z.literal(s)) as [z.ZodLiteral<301>, z.ZodLiteral<302>, z.ZodLiteral<307>, z.ZodLiteral<308>])).optional(),
});
async function need(ctx: AdminContext, action: string) {
if (!(await ctx.can(`redirects:${action}`))) throw new SiteError('forbidden', `forbidden: redirects:${action}`);
}
export const redirectsAdminResource: AdminResource<Redirect> = {
name: 'redirects',
label: { singular: 'Redirect', plural: 'Redirects' },
group: 'Settings',
fields: [
{ name: 'fromPath', label: 'From', type: 'text', required: true, list: true },
{ name: 'to', label: 'To', type: 'text', required: true, list: true },
{ name: 'status', label: 'Status', type: 'select', list: true, options: REDIRECT_STATUSES.map((s) => ({ value: String(s), label: String(s) })) },
{ name: 'hits', label: 'Hits', type: 'number', readOnly: true, list: true },
],
input: RedirectInput,
title: (r) => `${r.fromPath} → ${r.to}`,
async list(q, ctx) {
await need(ctx, 'read');
const where = q.search ? or(ilike(redirects.fromPath, `%${q.search}%`), ilike(redirects.to, `%${q.search}%`)) : undefined;
const [total] = await getDb().select({ n: count() }).from(redirects).where(where);
const rows = await getDb().select().from(redirects).where(where).orderBy(asc(redirects.fromPath)).limit(q.pageSize).offset((Math.max(q.page, 1) - 1) * q.pageSize);
return { rows, total: total?.n ?? 0 };
},
async get(id, ctx) {
await need(ctx, 'read');
const [row] = await getDb().select().from(redirects).where(eq(redirects.id, id));
return row ?? null;
},
async create(input, ctx) {
await need(ctx, 'create');
return upsertRedirect(RedirectInput.parse(input));
},
async update(id, input, ctx) {
await need(ctx, 'update');
return updateRedirect(id, RedirectInput.parse(input));
},
async delete(id, ctx) {
await need(ctx, 'delete');
await deleteRedirect(id);
},
};
/** Los tres recursos de este paquete, para `src/genpm/admin.ts`. */
export const siteAdminResources = () => [contentAdminResource('site_settings'), contentAdminResource('menus'), redirectsAdminResource];
Dieses Paket deklariert keine MCP-Server.
| Version | Commit | Veröffentlicht | Prüfung |
|---|---|---|---|
| 1.1.0 | b7c13fa | vor 3 Stunden | Prüfung bestanden |
- npm
- zod ^4.0.0
- vorgeschlagen
- GenPM schlägt den npm-Befehl vor und führt ihn nur aus, wenn du zustimmst.
- Verwendet von (2)
- @core/kit-cms ^1.0.0@core/seo ^1.0.0
- Prüfung
- Prüfung bestanden · 0 Befunde
- Commit
- v1.1.0 → b7c13fa9b316c9880bceb62d510ee9661a76ae08 · nach dem Abruf verifiziert
- Skripte
- Keine. GenPM führt niemals Paketcode aus.
- Lizenz
- MIT
- Qualität
- 100/100
- Anerkannte Lizenzerfüllt
- AGENTS.md erklärt den Zweckerfüllt
- AGENTS.md enthält Integrationsschritteerfüllt
- AGENTS.md nennt Konventionen oder Verboteerfüllt
- Enthält Testserfüllt
- Sicherheitsscan bestandenerfüllt
- In den letzten 6 Monaten veröffentlichterfüllt
- Verifizierter Herausgebererfüllt
- Zusammenfassung und Schlagwörtererfüllt
- Meldung
- Stimmt etwas nicht?