@cardinali-studio/adminkit

中文文档
2.2.49发布于 2026/11/050BSDTypeScriptES Module近 30 天下载 2009

Motore riusabile di un pannello di amministrazione per siti Hugo su Cloudflare Pages: codec del front matter guidato dallo schema, form React, libreria media su R2. NIENTE di specifico di un sito qui dentro.

原始使用文档
文档目录18 节
  1. Transfer email — 2.2.45
  2. The important property
  3. Install
  4. A complete codec example
  5. Deleting and retiring fields
  6. Mounting the admin application
  7. Cloudflare Pages Functions
  8. Persistence and publication
  9. Entry points
  10. What this is not
  11. Delivery ZIP compatibility (2.2.38)
  12. Permanent delivery storage cleanup
  13. Development
  14. License
  15. Delivery dashboard
  16. Transfer dashboard and creation (2.2.42)
  17. Illustrated Transfer guide (2.2.44)
  18. Cloudflare Email Service and shared B2 buckets

AdminKit

AdminKit is the administration engine we use for small, content-driven websites built with Hugo and deployed on Cloudflare Pages.

It turns a Zod schema into three things that must agree:

  • the model edited in the browser;
  • the YAML front matter stored in the repository;
  • the validation performed by the server.

There is no second list of fields hidden in the UI, and no second serializer hidden in a Worker. A field is declared once. The rest of the system reads that declaration.

The package also contains the React administration shell, a media library for R2, Cloudflare Pages Functions, and the persistence primitives used by the D1 publication path.

Transfer email — 2.2.45

Transfer supports independent owner notifications and automatic recipient emails through Resend, with optional recipient address, explicit opt-in, durable retries, send history and confirmed resends. Transfer settings include the editable owner email and owner notifications for each newly requested download. Sending stays disabled until configured per site. Install migration 0012_delivery_email.sql and configure the server-only email variables described in the integration guide. Existing transfers are not emailed automatically. The illustrated Transfer guide includes the email workflow and explains provider acceptance versus delivery.

The important property

An admin panel should not damage a file merely because it does not understand all of it.

AdminKit preserves the Markdown body, unknown front-matter keys, and their comments. An omitted managed field means “leave it alone”, not “delete it”. Before overwriting an existing file, checkFidelity() can prove that reading and writing it again is byte-for-byte stable.

If the codec cannot reproduce a file, it says so. Refusing a save is better than producing plausible-looking YAML with missing data.

Install

npm install @cardinali-studio/adminkit zod

The core codec needs Zod 4. The React application and form exports also use the optional peer dependencies listed in package.json; install the ones required by the entry points you use.

The package is ESM-only.

A complete codec example

Each field has two pieces of metadata:

  • ui describes how a human edits it;
  • fm describes how it lives in the file.
import * as z from "zod";
import {
  checkFidelity,
  deserializeItem,
  fm,
  serializeItem,
  ui,
  yamlStr,
} from "@cardinali-studio/adminkit";

const Article = {
  title: z.string().min(1).max(120)
    .register(ui, {
      label: "Title",
      widget: "text",
      group: "content",
    })
    .register(fm, {
      keys: ["title"],
      encode: (value) => [`title: ${yamlStr(value)}`],
      decode: (raw) => String(raw.title ?? ""),
    }),

  published: z.boolean()
    .register(ui, {
      label: "Published",
      widget: "boolean",
      group: "visibility",
    })
    .register(fm, {
      keys: ["visible"],
      encode: (value) => [`visible: ${value === true}`],
      decode: (raw) => raw.visible === true,
    }),
};

const markdown = `---
# Kept with the field.
title: "A small page"
visible: false

# AdminKit does not own this key, so it stays untouched.
build-note: "keep me"
---
The Markdown body stays untouched too.
`;

if (!checkFidelity(markdown, Article).ok) {
  throw new Error("This file cannot be edited without loss");
}

const article = deserializeItem(markdown, Article);

const updated = serializeItem(
  { ...article, published: true },
  { shape: Article, oldMd: markdown },
);

Notice that the model calls the boolean published while the file calls it visible. That conversion belongs to the schema. Hugo templates, React components, and API handlers should not each invent their own version of it.

Deleting and retiring fields

Absence is not deletion. To clear a value, pass the empty value understood by its encoder and return no YAML lines.

When a key is obsolete, retired(schema, keys) keeps ownership of the old key while emitting nothing. Existing files then lose that key on their next intentional save, without maintaining a separate migration list.

Mounting the admin application

The application is deliberately mounted as one unit:

import { mountAdmin, type SiteAdminConfig } from "@cardinali-studio/adminkit/app";
import "@cardinali-studio/adminkit/theme.css";
import "./site-theme.css";

const site: SiteAdminConfig = {
  // Site identity, page types, schemas, enabled modules and persistence mode.
  // TypeScript checks the complete contract here.
};

mountAdmin({
  site,
  root: document.querySelector("#root"),
  storagePrefix: "example-admin",
});

Import the site's theme after AdminKit's theme. This lets a site replace the CSS custom properties without increasing selector specificity.

storagePrefix is part of the site's browser data. Changing it on a live installation silently resets saved media preferences, so migrations should keep the old value.

The shell currently speaks Italian. Content can be monolingual or multilingual; the language of the administration interface is a separate concern.

Cloudflare Pages Functions

Cloudflare routes Functions by file, while an npm package cannot create files in a consuming project's functions/ directory. A site therefore keeps two small adapters.

The API catch-all:

// functions/admin/api/[[path]].js
import { handleAdminApi } from "@cardinali-studio/adminkit/functions";
import { serverConfig } from "../../../src/admin/site-config.js";

export function onRequest(context) {
  return handleAdminApi(context, serverConfig);
}

And the access middleware:

// functions/admin/_middleware.js
export { adminAccess as onRequest } from "@cardinali-studio/adminkit/functions";

Site-specific endpoints may live beside these files. More specific Cloudflare routes take precedence over the catch-all.

The server configuration is site-owned. It supplies schemas, repository details, enabled modules, storage bindings and, in D1 mode, the complete document validator. Missing optional modules fail closed.

Persistence and publication

The original persistence path writes Markdown through GitHub and lets Hugo build it. The newer path stores immutable revisions in D1 and publishes an exact release snapshot.

The D1 modules are intentionally lower-level than the admin shell. They provide:

  • deterministic request checksums and idempotent saves;
  • compare-and-swap protection for concurrent drafts;
  • immutable, checksum-covered release exports;
  • a single deployment lane for code and content;
  • copy-first media moves and delete journals for R2;
  • versioned site-access secrets;
  • pure release materialization, with filesystem effects left to CI.

They do not decide what a site's document means. Validation and materialization remain site-owned.

Entry points

ImportPurpose
@cardinali-studio/adminkitSchema registries, front-matter codec, API client, i18n, persistence and release primitives
@cardinali-studio/adminkit/appComplete React administration shell
@cardinali-studio/adminkit/formSchema form and media components
@cardinali-studio/adminkit/functionsCloudflare Pages API router and access middleware
@cardinali-studio/adminkit/materializerPure release-to-files materialization
@cardinali-studio/adminkit/previewLocal preview component
@cardinali-studio/adminkit/testingContent-stability checks for consuming sites
@cardinali-studio/adminkit/theme.cssDefault design tokens

What this is not

AdminKit is not a generic headless CMS, a form builder, or a promise to support every YAML construct.

It is opinionated about the useful narrow case: Hugo content, React 19, Zod 4, Cloudflare Pages, R2 and optionally D1. The front-matter emitter supports the structures used by that system and throws on values it cannot represent safely.

This narrowness is intentional. A smaller system with explicit failure modes is easier to trust around content that people have edited by hand.

Delivery ZIP compatibility (2.2.38)

New delivery archives use STORE members with their CRC and sizes in each local header, without data descriptors. This prevents streaming extractors from mistaking descriptor-shaped bytes inside a photograph for the end of the file. CRC preparation reads bounded 4 MiB slices and remains pausable and cancellable; source changes fail closed. ZIP64 sizes and offsets are promoted independently.

Persistent checkpoints record store-header-v1. Checkpoints from earlier releases continue with store-descriptor-v1, preserving confirmed multipart bytes and derivative retries. The update affects newly generated archives; it does not rewrite existing stored ZIPs. Consumers must update the dependency, rebuild and redeploy.

The regression includes 208 synthetic members with the collision at member 53, streaming and indexed readers, ZIP64 offsets, and byte-identical resume against fixtures from 2.2.37.

Permanent delivery storage cleanup

Delivery cleanup permanently deletes all versions and delete markers in the removed package's exact storage prefix, and aborts unfinished multipart uploads. Successful deletion requires a subsequent empty listing; a failed or partial sweep keeps the database work for retry. No bucket-wide cleanup or lifecycle is installed.

Apply dist/functions/delivery-public/migrations/0010_delivery_storage_cleanup.sql to the consumer's delivery D1 before deploying this code. The new delivery_storage_cleanup table has no foreign keys: its scoped tombstones survive removal of a delivery, so a PUT or multipart completion already in flight cannot become permanently untracked. The housekeeping caller must continue invoking purgeDue independently of visits to the admin or public pages. Scopes are checked again after 15 minutes in the first hour, then daily, subject to the caller's request budget and backlog. Tombstones are deliberately retained indefinitely. last_error records failed sweeps; last_checked_at records successful empty verification.

Expired deliveries cannot request new upload signatures or start/finalize new packages. An uploading package retains its one-hour metadata grace period; its durable scope also catches requests finishing after that grace period. A late write can occupy space temporarily until the next successful sweep.

The opt-in live regression scripts/verify-delivery-storage.mjs uses a new random prefix and an in-memory D1 database. It creates versions, a delete marker, an unfinished multipart upload, and a PUT authorized before deletion. It checks zero remaining versions/bytes/uploads after both deletion and expiration, and compares all pre-existing version IDs before and after. Every mutation outside the new prefix is refused. Configuration and secret-file paths are arguments; credentials are never printed. This test does not deploy a Worker or change lifecycle rules.

Do not deploy this cleanup against a populated production bucket when existing objects must be preserved: pending/expired deliveries would become eligible for permanent deletion. The migration alone is additive and does not enqueue old data.

Development

npm test
npm run build
npm run check

npm run check rebuilds the package and runs the Node test suite. The tests include byte-identical codec round trips, malformed-input rejection, import resolution, concurrency conflicts, release invariants, media-operation transitions, and a guard against site-specific code entering the shared package.

Generated files live in dist/ and are the only application files included in the npm tarball, in addition to npm's standard package metadata and this README.

License

0BSD. Use it, copy it, change it, ship it. Attribution is appreciated but not required.

Delivery dashboard

The delivery overview reads download requests, not completed downloads. Active transfers have ready packages, unexpired files and at least one valid link.

Before enabling DELIVERY_STORAGE_DASHBOARD=true, apply dist/functions/delivery-public/migrations/0011_delivery_storage_dashboard.sql to the consumer's delivery D1 database. In the consumer's existing scheduled handler, call verifyDeliveryStorage(env, {maxCalls: 4}) from @cardinali-studio/adminkit/functions/_shared/delivery-storage-dashboard.js after housekeeping. Run the handler every 15 minutes: completed inventories refresh hourly; larger inventories resume across invocations. Admin polling also resumes due work, and “Verifica ora” forces a fresh check.

The read-only scan sums all B2 object versions and uploaded multipart parts, including hidden versions and unassigned keys. It stores only complete totals, retaining the previous snapshot on failure, and shows the last completion time. Concurrent B2 writes can change storage during the scan; this is a measured inventory, not the provider's billing statement. Dashboard checks never delete objects. The existing preservation policy remains independent of this feature.

Transfer dashboard and creation (2.2.42)

Both the full admin and mountDeliveryAdmin expose a Dashboard button above New transfer. New transfers start with gallery and low-resolution conversion off, JPG conversion selected, and a 60-day expiry. Existing packages keep their options. Delivery options precede file selection; unavailable features are collapsed.

A site may supply deliveryPricing in SiteAdminConfig, or pricing in DeliveryAdminOptions: storageUsdPerTb30Days, freeStorageGb, downloadUsdPerGb, freeDownloadMultiplier, and ISO date verifiedAt. The dashboard projects the last measured storage across the current calendar month; it does not reconstruct past usage, subtract a shared free allowance, or infer actual download bytes. Missing inventory is shown as unavailable, never zero. The information dialog explains those limits. Rates are supplied by the consumer, not inferred from storage credentials or provider names. The dedicated delivery entry now initializes its own module configuration; consumers must remove the old internal setSiteConfig workaround.

2.2.43 aligns the dedicated delivery navigation with the full shell: shared icons, accessible labels and text wrappers preserve both admin interface variants.

Illustrated Transfer guide (2.2.44)

The dashboard links to the bundled Italian guide in a new tab. Both public admin mount functions recognize ?adminkit-guide=transfer on the existing protected admin path. The reader loads lazily, has no Transfer API hooks, supports chapter deep links and contains seven locally captured screenshots with synthetic data. Screenshots and guide CSS ship in dist/app/guide/; consumers using Vite bundle the relative image URLs automatically. No external documentation host, new route or credentials are required. Source and screenshot provenance: src/app/guide/ and scripts/preview-transfer-guide.mjs.

Cloudflare Email Service and shared B2 buckets

Transfer email also supports DELIVERY_EMAIL_PROVIDER=cloudflare, the native DELIVERY_EMAIL send binding and DELIVERY_EMAIL_ENABLED=true. Set ENVIRONMENT=production, a verified DELIVERY_EMAIL_FROM, the canonical DELIVERY_PUBLIC_BASE and the stable DELIVERY_SECRET; save owner/preferences in Transfer settings. Request and cron Workers need the same configuration. No Resend key is required. Use a binding restricted by sender (not only by owner recipient) to send customer links on Workers Paid. Explicit provider selection never falls back to Resend. Unknown/timeout results require review to prevent unsafe automatic duplicate sends; documented rate/quota rejections can retry. Non-production tests must inject an explicit mock binding.

DELIVERY_STORAGE_PREFIX=deliveries/consumer-a optionally isolates object keys, signed URLs, inventories and version-aware deletion in a shared private bucket. Independent projects must keep separate transfer databases. Consumers omitting the setting retain existing paths. Keep the cleanup migration and persistent cleanup records so outstanding signed writes are collected after deletion.

版本与发布记录

@cardinali-studio/adminkit 共发布 49 个版本,首次发布于 2026/08/12;过去 12 个月发布了 49 个版本。

版本发布日期距今
2.2.49latest2026/11/054 天前
2.2.482026/10/2416 天前
2.2.472026/10/2416 天前
2.2.462026/10/2416 天前
2.2.452026/10/2317 天前
2.2.442026/10/2318 天前

其他发布通道:next → 2.1.0-rc.4。

关于 @cardinali-studio/adminkit 的常见问题

@cardinali-studio/adminkit 是什么?

Motore riusabile di un pannello di amministrazione per siti Hugo su Cloudflare Pages: codec del front matter guidato dallo schema, form React, libreria media su R2. NIENTE di specifico di un sito qui dentro.

如何安装 @cardinali-studio/adminkit?

在项目目录运行 npm install @cardinali-studio/adminkit,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:zod(^4)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@cardinali-studio/adminkit 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cardinali-studio/adminkit 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@cardinali-studio/adminkit 可以商用吗?

@cardinali-studio/adminkit 使用 0BSD 许可。几乎没有限制:可自由使用、修改和分发,连保留版权声明也不要求。以上为概括,不构成法律意见,请以许可原文为准。

@cardinali-studio/adminkit 的下载量有多少?

根据 npm 官方下载统计,@cardinali-studio/adminkit 在 2026/10/09 – 2026/11/07 这 30 天内被下载 2,009 次,日均约 67 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cardinali-studio/adminkit 还在维护吗?

最新版本 2.2.49 发布于 2026/11/05(4 天前);至今共发布 49 个版本,其中过去 12 个月发布了 49 个;首次发布于 2026/08/12。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。