@cardinali-studio/adminkit 2.2.24 → 2.2.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/LICENSE +12 -0
  2. package/README.md +213 -0
  3. package/package.json +2 -2
package/LICENSE ADDED
@@ -0,0 +1,12 @@
1
+ Copyright (C) 2026 Emanuele Cardinali
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted.
5
+
6
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
7
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
8
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
9
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
10
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
11
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
12
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # AdminKit
2
+
3
+ AdminKit is the administration engine we use for small, content-driven websites built with Hugo and deployed on Cloudflare Pages.
4
+
5
+ It turns a Zod schema into three things that must agree:
6
+
7
+ - the model edited in the browser;
8
+ - the YAML front matter stored in the repository;
9
+ - the validation performed by the server.
10
+
11
+ 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.
12
+
13
+ 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.
14
+
15
+ ## The important property
16
+
17
+ An admin panel should not damage a file merely because it does not understand all of it.
18
+
19
+ 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.
20
+
21
+ If the codec cannot reproduce a file, it says so. Refusing a save is better than producing plausible-looking YAML with missing data.
22
+
23
+ ## Install
24
+
25
+ ```sh
26
+ npm install @cardinali-studio/adminkit zod
27
+ ```
28
+
29
+ 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.
30
+
31
+ The package is ESM-only.
32
+
33
+ ## A complete codec example
34
+
35
+ Each field has two pieces of metadata:
36
+
37
+ - `ui` describes how a human edits it;
38
+ - `fm` describes how it lives in the file.
39
+
40
+ ```ts
41
+ import * as z from "zod";
42
+ import {
43
+ checkFidelity,
44
+ deserializeItem,
45
+ fm,
46
+ serializeItem,
47
+ ui,
48
+ yamlStr,
49
+ } from "@cardinali-studio/adminkit";
50
+
51
+ const Article = {
52
+ title: z.string().min(1).max(120)
53
+ .register(ui, {
54
+ label: "Title",
55
+ widget: "text",
56
+ group: "content",
57
+ })
58
+ .register(fm, {
59
+ keys: ["title"],
60
+ encode: (value) => [`title: ${yamlStr(value)}`],
61
+ decode: (raw) => String(raw.title ?? ""),
62
+ }),
63
+
64
+ published: z.boolean()
65
+ .register(ui, {
66
+ label: "Published",
67
+ widget: "boolean",
68
+ group: "visibility",
69
+ })
70
+ .register(fm, {
71
+ keys: ["visible"],
72
+ encode: (value) => [`visible: ${value === true}`],
73
+ decode: (raw) => raw.visible === true,
74
+ }),
75
+ };
76
+
77
+ const markdown = `---
78
+ # Kept with the field.
79
+ title: "A small page"
80
+ visible: false
81
+
82
+ # AdminKit does not own this key, so it stays untouched.
83
+ build-note: "keep me"
84
+ ---
85
+ The Markdown body stays untouched too.
86
+ `;
87
+
88
+ if (!checkFidelity(markdown, Article).ok) {
89
+ throw new Error("This file cannot be edited without loss");
90
+ }
91
+
92
+ const article = deserializeItem(markdown, Article);
93
+
94
+ const updated = serializeItem(
95
+ { ...article, published: true },
96
+ { shape: Article, oldMd: markdown },
97
+ );
98
+ ```
99
+
100
+ 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.
101
+
102
+ ### Deleting and retiring fields
103
+
104
+ Absence is not deletion. To clear a value, pass the empty value understood by its encoder and return no YAML lines.
105
+
106
+ 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.
107
+
108
+ ## Mounting the admin application
109
+
110
+ The application is deliberately mounted as one unit:
111
+
112
+ ```ts
113
+ import { mountAdmin, type SiteAdminConfig } from "@cardinali-studio/adminkit/app";
114
+ import "@cardinali-studio/adminkit/theme.css";
115
+ import "./site-theme.css";
116
+
117
+ const site: SiteAdminConfig = {
118
+ // Site identity, page types, schemas, enabled modules and persistence mode.
119
+ // TypeScript checks the complete contract here.
120
+ };
121
+
122
+ mountAdmin({
123
+ site,
124
+ root: document.querySelector("#root"),
125
+ storagePrefix: "example-admin",
126
+ });
127
+ ```
128
+
129
+ Import the site's theme after AdminKit's theme. This lets a site replace the CSS custom properties without increasing selector specificity.
130
+
131
+ `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.
132
+
133
+ The shell currently speaks Italian. Content can be monolingual or multilingual; the language of the administration interface is a separate concern.
134
+
135
+ ## Cloudflare Pages Functions
136
+
137
+ 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.
138
+
139
+ The API catch-all:
140
+
141
+ ```js
142
+ // functions/admin/api/[[path]].js
143
+ import { handleAdminApi } from "@cardinali-studio/adminkit/functions";
144
+ import { serverConfig } from "../../../src/admin/site-config.js";
145
+
146
+ export function onRequest(context) {
147
+ return handleAdminApi(context, serverConfig);
148
+ }
149
+ ```
150
+
151
+ And the access middleware:
152
+
153
+ ```js
154
+ // functions/admin/_middleware.js
155
+ export { adminAccess as onRequest } from "@cardinali-studio/adminkit/functions";
156
+ ```
157
+
158
+ Site-specific endpoints may live beside these files. More specific Cloudflare routes take precedence over the catch-all.
159
+
160
+ 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.
161
+
162
+ ## Persistence and publication
163
+
164
+ 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.
165
+
166
+ The D1 modules are intentionally lower-level than the admin shell. They provide:
167
+
168
+ - deterministic request checksums and idempotent saves;
169
+ - compare-and-swap protection for concurrent drafts;
170
+ - immutable, checksum-covered release exports;
171
+ - a single deployment lane for code and content;
172
+ - copy-first media moves and delete journals for R2;
173
+ - versioned site-access secrets;
174
+ - pure release materialization, with filesystem effects left to CI.
175
+
176
+ They do not decide what a site's document means. Validation and materialization remain site-owned.
177
+
178
+ ## Entry points
179
+
180
+ | Import | Purpose |
181
+ | --- | --- |
182
+ | `@cardinali-studio/adminkit` | Schema registries, front-matter codec, API client, i18n, persistence and release primitives |
183
+ | `@cardinali-studio/adminkit/app` | Complete React administration shell |
184
+ | `@cardinali-studio/adminkit/form` | Schema form and media components |
185
+ | `@cardinali-studio/adminkit/functions` | Cloudflare Pages API router and access middleware |
186
+ | `@cardinali-studio/adminkit/materializer` | Pure release-to-files materialization |
187
+ | `@cardinali-studio/adminkit/preview` | Local preview component |
188
+ | `@cardinali-studio/adminkit/testing` | Content-stability checks for consuming sites |
189
+ | `@cardinali-studio/adminkit/theme.css` | Default design tokens |
190
+
191
+ ## What this is not
192
+
193
+ AdminKit is not a generic headless CMS, a form builder, or a promise to support every YAML construct.
194
+
195
+ 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.
196
+
197
+ This narrowness is intentional. A smaller system with explicit failure modes is easier to trust around content that people have edited by hand.
198
+
199
+ ## Development
200
+
201
+ ```sh
202
+ npm test
203
+ npm run build
204
+ npm run check
205
+ ```
206
+
207
+ `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.
208
+
209
+ 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.
210
+
211
+ ## License
212
+
213
+ [0BSD](./LICENSE). Use it, copy it, change it, ship it. Attribution is appreciated but not required.
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@cardinali-studio/adminkit",
3
- "version": "2.2.24",
3
+ "version": "2.2.25",
4
4
  "type": "module",
5
5
  "description": "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.",
6
- "license": "UNLICENSED",
6
+ "license": "0BSD",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
9
  "exports": {