create-kide-app 0.0.32 → 0.0.33
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.
package/index.js
CHANGED
|
@@ -384,10 +384,7 @@ async function main() {
|
|
|
384
384
|
if (databaseId) {
|
|
385
385
|
const wranglerPath = path.join(projectDir, "wrangler.toml");
|
|
386
386
|
let wranglerContent = readFileSync(wranglerPath, "utf-8");
|
|
387
|
-
wranglerContent = wranglerContent.replace(
|
|
388
|
-
/database_id = "" #[^\n]*/,
|
|
389
|
-
`database_id = "${databaseId}"`,
|
|
390
|
-
);
|
|
387
|
+
wranglerContent = wranglerContent.replace(/database_id = "" #[^\n]*/, `database_id = "${databaseId}"`);
|
|
391
388
|
writeFileSync(wranglerPath, wranglerContent);
|
|
392
389
|
}
|
|
393
390
|
|
|
@@ -466,16 +463,27 @@ async function main() {
|
|
|
466
463
|
}
|
|
467
464
|
} else {
|
|
468
465
|
if (cf.deployed && cf.url) {
|
|
469
|
-
p.note(
|
|
466
|
+
p.note(
|
|
467
|
+
[
|
|
468
|
+
`Live at: ${cf.url}`,
|
|
469
|
+
`Admin: ${cf.url}/admin`,
|
|
470
|
+
"",
|
|
471
|
+
`cd ${projectName}`,
|
|
472
|
+
"",
|
|
473
|
+
"Local development:",
|
|
474
|
+
` ${pm.run} dev`,
|
|
475
|
+
"",
|
|
476
|
+
"Redeploy:",
|
|
477
|
+
" pnpm run deploy",
|
|
478
|
+
].join("\n"),
|
|
479
|
+
"🎉 Your Kide CMS is live",
|
|
480
|
+
);
|
|
470
481
|
p.outro("Project created!");
|
|
471
482
|
} else {
|
|
472
483
|
const lines = [`cd ${projectName}`];
|
|
473
484
|
const remaining = [];
|
|
474
485
|
if (!cf.d1Created) {
|
|
475
|
-
remaining.push(
|
|
476
|
-
` ${pm.dlx} wrangler d1 create ${projectName}-db`,
|
|
477
|
-
" # Copy the database_id to wrangler.toml",
|
|
478
|
-
);
|
|
486
|
+
remaining.push(` ${pm.dlx} wrangler d1 create ${projectName}-db`, " # Copy the database_id to wrangler.toml");
|
|
479
487
|
}
|
|
480
488
|
if (!cf.r2Created) {
|
|
481
489
|
remaining.push(` ${pm.dlx} wrangler r2 bucket create ${projectName}-assets`);
|
package/package.json
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Kide CMS
|
|
2
|
+
|
|
3
|
+
Code-first CMS built inside Astro 6.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/cms/cms.config.ts # Collection definitions (source of truth)
|
|
9
|
+
src/cms/collections/ # Individual collection files
|
|
10
|
+
src/cms/adapters/ # Database, storage, email adapters
|
|
11
|
+
src/cms/internals/ # Runtime, generator, seed scripts
|
|
12
|
+
src/cms/.generated/ # Auto-generated — never edit
|
|
13
|
+
src/components/ # Astro/React components
|
|
14
|
+
src/layouts/ # Page layouts
|
|
15
|
+
src/pages/ # Routes
|
|
16
|
+
src/styles/admin.css # Admin theme overrides (shadcn variables)
|
|
17
|
+
src/styles/public.css # Public site styles (plain Tailwind)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm dev # start dev server (auto-generates schema + pushes DB)
|
|
24
|
+
pnpm build # production build
|
|
25
|
+
pnpm cms:generate # regenerate .generated/ from cms.config.ts
|
|
26
|
+
pnpm cms:seed # seed database with demo content (if configured)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Rules
|
|
30
|
+
|
|
31
|
+
- Never edit files in `src/cms/.generated/` — they are overwritten on every generation.
|
|
32
|
+
- Always query content through the typed local API: `cms.posts.find()`, `cms.pages.findOne()`, etc. Never bypass it with raw DB queries.
|
|
33
|
+
- DB columns use `snake_case`, TypeScript fields use `camelCase`. System fields are prefixed with `_`.
|
|
34
|
+
- Rich text is stored as JSON AST, never HTML or Markdown.
|
|
35
|
+
- Admin styles use shadcn CSS variables. Public site uses plain Tailwind colors — no shared styles between admin and public.
|
|
36
|
+
- Use the `cn()` utility from `@kidecms/core/admin/lib/utils` for conditional class names in admin components.
|
|
37
|
+
- `labelField` on collections controls display name in the admin (fallback: title, name, or first text field).
|
|
38
|
+
|
|
39
|
+
## Stack
|
|
40
|
+
|
|
41
|
+
Astro 6, React 19, Drizzle ORM, Zod, Tiptap, shadcn/ui, Tailwind CSS v4
|
|
42
|
+
|
|
43
|
+
## Field Types
|
|
44
|
+
|
|
45
|
+
Defined in collection files via `fields.*`. All fields share base options: `label`, `description`, `required`, `defaultValue`, `indexed`, `unique`, `translatable`, `condition`, `admin`, `access`.
|
|
46
|
+
|
|
47
|
+
| Field | Type-specific options |
|
|
48
|
+
| ---------- | ------------------------------------------------------------------------------- |
|
|
49
|
+
| `text` | `maxLength?: number` |
|
|
50
|
+
| `slug` | `from?: string` — field to auto-generate slug from |
|
|
51
|
+
| `email` | _(base only)_ |
|
|
52
|
+
| `number` | _(base only)_ |
|
|
53
|
+
| `boolean` | _(base only)_ |
|
|
54
|
+
| `date` | _(base only)_ |
|
|
55
|
+
| `select` | `options: string[]` **(required)** |
|
|
56
|
+
| `richText` | _(base only)_ — stored as JSON AST `{ type: "root", children: RichTextNode[] }` |
|
|
57
|
+
| `image` | _(base only)_ — stores asset reference |
|
|
58
|
+
| `relation` | `collection: string` **(required)**, `hasMany?: boolean` |
|
|
59
|
+
| `array` | `of: FieldConfig` **(required)** — field config for each item |
|
|
60
|
+
| `json` | `schema?: string` |
|
|
61
|
+
| `blocks` | `types: Record<string, Record<string, FieldConfig>>` **(required)** |
|
|
62
|
+
|
|
63
|
+
`admin` sub-options: `component`, `placeholder`, `position` (`"content"` | `"sidebar"`), `rows`, `help`, `hidden`.
|
|
64
|
+
|
|
65
|
+
`condition`: `{ field: string; value: string | string[] | boolean }` — show/hide field based on another field's value.
|
|
66
|
+
|
|
67
|
+
`access`: `{ read?, update? }` — functions receiving `{ user, doc, operation, collection }`, return `boolean`.
|
|
68
|
+
|
|
69
|
+
## Live Preview
|
|
70
|
+
|
|
71
|
+
The CMS auto-injects a preview script on every page. To make fields update in real time:
|
|
72
|
+
|
|
73
|
+
1. Add `?preview` to the page URL.
|
|
74
|
+
2. Add `data-cms="{fieldName}"` attributes to elements that display field values.
|
|
75
|
+
|
|
76
|
+
For rich text and blocks fields, the preview system uses server-side rendering via `/api/cms/preview/render` (dev only). Simple text fields update via `textContent` directly.
|
|
@@ -3,12 +3,15 @@ const { eyebrow, heading, body, ctaLabel, ctaHref } = Astro.props;
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
<section class="text-center">
|
|
6
|
-
{eyebrow && <p class="mb-2 text-xs font-semibold
|
|
6
|
+
{eyebrow && <p class="mb-2 text-xs font-semibold tracking-wide text-teal-700 uppercase">{eyebrow}</p>}
|
|
7
7
|
<h1 class="mb-3 text-3xl font-semibold">{heading}</h1>
|
|
8
8
|
{body && <p class="mb-4 text-gray-500">{body}</p>}
|
|
9
9
|
{
|
|
10
10
|
ctaLabel && ctaHref && (
|
|
11
|
-
<a
|
|
11
|
+
<a
|
|
12
|
+
href={String(ctaHref)}
|
|
13
|
+
class="mt-4 inline-block rounded-md bg-teal-700 px-4 py-2 text-sm text-white no-underline"
|
|
14
|
+
>
|
|
12
15
|
{ctaLabel}
|
|
13
16
|
</a>
|
|
14
17
|
)
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
if (new URLSearchParams(location.search).has("preview")) {
|
|
2
|
-
const ch = new BroadcastChannel("cms-preview");
|
|
3
|
-
let pending = 0;
|
|
4
|
-
|
|
5
|
-
ch.onmessage = (e: MessageEvent) => {
|
|
6
|
-
const d = e.data;
|
|
7
|
-
|
|
8
|
-
if (d.type === "reload") {
|
|
9
|
-
location.reload();
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const els = document.querySelectorAll<HTMLElement>(`[data-cms="${d.field}"]`);
|
|
14
|
-
if (!els.length) return;
|
|
15
|
-
|
|
16
|
-
if (d.render) {
|
|
17
|
-
const id = ++pending;
|
|
18
|
-
fetch("/api/cms/preview/render", {
|
|
19
|
-
method: "POST",
|
|
20
|
-
headers: { "Content-Type": "application/json" },
|
|
21
|
-
body: JSON.stringify({ type: d.render, data: d.value }),
|
|
22
|
-
})
|
|
23
|
-
.then((r) => (r.ok ? r.text() : null))
|
|
24
|
-
.then((html) => {
|
|
25
|
-
// Endpoint may not exist in production builds (only dev) — silently skip
|
|
26
|
-
if (html != null && id === pending) els.forEach((el) => (el.innerHTML = html));
|
|
27
|
-
})
|
|
28
|
-
.catch(() => {});
|
|
29
|
-
} else if (d.html != null) {
|
|
30
|
-
els.forEach((el) => (el.innerHTML = d.html));
|
|
31
|
-
} else {
|
|
32
|
-
els.forEach((el) => (el.textContent = d.value));
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
}
|