@kidecms/core 0.1.5 → 0.1.7
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/admin/components/DocumentActions.tsx +13 -5
- package/admin/layouts/AdminLayout.astro +2 -1
- package/dist/api.js +6 -0
- package/dist/integration.js +37 -0
- package/dist/webhooks.js +71 -0
- package/package.json +1 -1
|
@@ -149,11 +149,19 @@ export default function DocumentActions({
|
|
|
149
149
|
const form = document.createElement("form");
|
|
150
150
|
form.method = "post";
|
|
151
151
|
form.action = restoreEndpoint;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
152
|
+
|
|
153
|
+
const addInput = (name: string, value: string) => {
|
|
154
|
+
const input = document.createElement("input");
|
|
155
|
+
input.type = "hidden";
|
|
156
|
+
input.name = name;
|
|
157
|
+
input.value = value;
|
|
158
|
+
form.appendChild(input);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
addInput("_action", "restore");
|
|
162
|
+
addInput("version", String(version));
|
|
163
|
+
addInput("redirectTo", redirectTo ?? window.location.pathname + window.location.search);
|
|
164
|
+
|
|
157
165
|
document.body.appendChild(form);
|
|
158
166
|
form.submit();
|
|
159
167
|
};
|
|
@@ -167,7 +167,8 @@ navItems.sort((a, b) => a.weight - b.weight);
|
|
|
167
167
|
<meta name="viewport" content="width=device-width" />
|
|
168
168
|
<link
|
|
169
169
|
rel="icon"
|
|
170
|
-
|
|
170
|
+
type="image/svg+xml"
|
|
171
|
+
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='85' height='107' viewBox='0 0 85 107' fill='none'%3E%3Cpath d='M27.5894 91.1365C22.7555 86.7178 21.3444 77.4335 23.3583 70.7072C26.8503 74.948 31.6888 76.2914 36.7005 77.0497C44.4375 78.2199 52.0359 77.7822 59.2232 74.2459C60.0454 73.841 60.8052 73.3027 61.7036 72.7574C62.378 74.714 62.5535 76.6892 62.318 78.6996C61.7452 83.5957 59.3086 87.3778 55.4332 90.2448C53.8835 91.3916 52.2437 92.4167 50.6432 93.4979C45.7262 96.8213 44.3959 100.718 46.2435 106.386C46.2874 106.525 46.3267 106.663 46.426 107C43.9155 105.876 42.0817 104.24 40.6845 102.089C39.2087 99.8193 38.5066 97.3081 38.4696 94.5909C38.4511 93.2686 38.4511 91.9345 38.2733 90.6309C37.8391 87.4527 36.3471 86.0297 33.5364 85.9478C30.6518 85.8636 28.37 87.6469 27.7649 90.4554C27.7187 90.6707 27.6517 90.8837 27.5847 91.1341L27.5894 91.1365Z' fill='white'/%3E%3Cpath d='M27.5894 91.1365C22.7555 86.7178 21.3444 77.4335 23.3583 70.7072C26.8503 74.948 31.6888 76.2914 36.7005 77.0497C44.4375 78.2199 52.0359 77.7822 59.2232 74.2459C60.0454 73.841 60.8052 73.3027 61.7036 72.7574C62.378 74.714 62.5535 76.6892 62.318 78.6996C61.7452 83.5957 59.3086 87.3778 55.4332 90.2448C53.8835 91.3916 52.2437 92.4167 50.6432 93.4979C45.7262 96.8213 44.3959 100.718 46.2435 106.386C46.2874 106.525 46.3267 106.663 46.426 107C43.9155 105.876 42.0817 104.24 40.6845 102.089C39.2087 99.8193 38.5066 97.3081 38.4696 94.5909C38.4511 93.2686 38.4511 91.9345 38.2733 90.6309C37.8391 87.4527 36.3471 86.0297 33.5364 85.9478C30.6518 85.8636 28.37 87.6469 27.7649 90.4554C27.7187 90.6707 27.6517 90.8837 27.5847 91.1341L27.5894 91.1365Z' fill='url(%23paint0_linear_1_59)'/%3E%3Cpath d='M0 69.5866C0 69.5866 14.3139 62.6137 28.6678 62.6137L39.4901 29.1204C39.8953 27.5007 41.0783 26.3999 42.4139 26.3999C43.7495 26.3999 44.9325 27.5007 45.3377 29.1204L56.1601 62.6137C73.1601 62.6137 84.8278 69.5866 84.8278 69.5866C84.8278 69.5866 60.5145 3.35233 60.467 3.21944C59.7692 1.2612 58.5911 0 57.0029 0H27.8274C26.2392 0 25.1087 1.2612 24.3634 3.21944C24.3108 3.34983 0 69.5866 0 69.5866Z' fill='white'/%3E%3Cdefs%3E%3ClinearGradient id='paint0_linear_1_59' x1='22.4702' y1='107' x2='69.1451' y2='84.9468' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23D83333'/%3E%3Cstop offset='1' stop-color='%23F041FF'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E"
|
|
171
172
|
/>
|
|
172
173
|
<title>{title}</title>
|
|
173
174
|
<script is:inline>
|
package/dist/api.js
CHANGED
|
@@ -5,6 +5,7 @@ import { getCollectionMap, getTranslatableFieldNames, isStructuralField } from "
|
|
|
5
5
|
import { getDb } from "./runtime";
|
|
6
6
|
import { getSchema } from "./schema";
|
|
7
7
|
import { cloneValue, createRichTextFromPlainText, slugify } from "./values";
|
|
8
|
+
import { dispatchWebhooks } from "./webhooks";
|
|
8
9
|
const now = () => new Date().toISOString();
|
|
9
10
|
const pick = (input, keys) => Object.fromEntries(keys.filter((key) => key in input).map((key) => [key, input[key]]));
|
|
10
11
|
const isJsonField = (field) => field.type === "richText" ||
|
|
@@ -405,6 +406,7 @@ export const createCms = (config) => {
|
|
|
405
406
|
}
|
|
406
407
|
const result = await this.findById(docId, {}, context);
|
|
407
408
|
await collection.hooks?.afterCreate?.(result, hookContext);
|
|
409
|
+
dispatchWebhooks(config, "create", slug, result, context.user);
|
|
408
410
|
return result;
|
|
409
411
|
},
|
|
410
412
|
async update(id, data, context = {}) {
|
|
@@ -479,6 +481,7 @@ export const createCms = (config) => {
|
|
|
479
481
|
}
|
|
480
482
|
const result = await this.findById(id, { status: "any" }, context);
|
|
481
483
|
await collection.hooks?.afterUpdate?.(result, hookContext);
|
|
484
|
+
dispatchWebhooks(config, "update", slug, result, context.user);
|
|
482
485
|
return result;
|
|
483
486
|
},
|
|
484
487
|
async delete(id, context = {}) {
|
|
@@ -499,6 +502,7 @@ export const createCms = (config) => {
|
|
|
499
502
|
await db.delete(tables.versions).where(eq(tables.versions._docId, id));
|
|
500
503
|
await db.delete(tables.main).where(eq(tables.main._id, id));
|
|
501
504
|
await collection.hooks?.afterDelete?.(existing, hookContext);
|
|
505
|
+
dispatchWebhooks(config, "delete", slug, existing, context.user);
|
|
502
506
|
},
|
|
503
507
|
async publish(id, context = {}) {
|
|
504
508
|
if (!collection.drafts)
|
|
@@ -529,6 +533,7 @@ export const createCms = (config) => {
|
|
|
529
533
|
await db.update(tables.main).set(updateValues).where(eq(tables.main._id, id));
|
|
530
534
|
const result = await this.findById(id, { status: "any" }, context);
|
|
531
535
|
await collection.hooks?.afterPublish?.(result, hookContext);
|
|
536
|
+
dispatchWebhooks(config, "publish", slug, result, context.user);
|
|
532
537
|
return result;
|
|
533
538
|
},
|
|
534
539
|
async unpublish(id, context = {}) {
|
|
@@ -559,6 +564,7 @@ export const createCms = (config) => {
|
|
|
559
564
|
await db.update(tables.main).set(updateValues).where(eq(tables.main._id, id));
|
|
560
565
|
const result = (await this.findById(id, { status: "any" }, context));
|
|
561
566
|
await collection.hooks?.afterUnpublish?.(result, hookContext);
|
|
567
|
+
dispatchWebhooks(config, "unpublish", slug, result, context.user);
|
|
562
568
|
return result;
|
|
563
569
|
},
|
|
564
570
|
async schedule(id, publishAt, unpublishAt, context = {}) {
|
package/dist/integration.js
CHANGED
|
@@ -149,6 +149,43 @@ export default function cmsIntegration(options) {
|
|
|
149
149
|
"virtual:kide/custom-fields": customFieldsBarrel,
|
|
150
150
|
},
|
|
151
151
|
},
|
|
152
|
+
// Pre-bundle admin component deps. Without this, Vite's dep scanner skips
|
|
153
|
+
// the admin/ source (it lives in node_modules), so packages like @tiptap/react
|
|
154
|
+
// are served raw — and their CJS sub-deps (use-sync-external-store/shim) break
|
|
155
|
+
// browser ESM imports. Use "@kidecms/core > X" syntax so Vite resolves the
|
|
156
|
+
// packages through @kidecms/core's nested node_modules under pnpm.
|
|
157
|
+
optimizeDeps: {
|
|
158
|
+
include: [
|
|
159
|
+
"@kidecms/core > @tiptap/react",
|
|
160
|
+
"@kidecms/core > @tiptap/starter-kit",
|
|
161
|
+
"@kidecms/core > @tiptap/extension-image",
|
|
162
|
+
"@kidecms/core > @tiptap/extension-link",
|
|
163
|
+
"@kidecms/core > @tiptap/markdown",
|
|
164
|
+
"@kidecms/core > @tiptap/core",
|
|
165
|
+
"@kidecms/core > @dnd-kit/core",
|
|
166
|
+
"@kidecms/core > @dnd-kit/sortable",
|
|
167
|
+
"@kidecms/core > @dnd-kit/utilities",
|
|
168
|
+
"@kidecms/core > @radix-ui/react-dropdown-menu",
|
|
169
|
+
"@kidecms/core > @radix-ui/react-popover",
|
|
170
|
+
"@kidecms/core > @base-ui/react/alert-dialog",
|
|
171
|
+
"@kidecms/core > @base-ui/react/button",
|
|
172
|
+
"@kidecms/core > @base-ui/react/checkbox",
|
|
173
|
+
"@kidecms/core > @base-ui/react/collapsible",
|
|
174
|
+
"@kidecms/core > @base-ui/react/dialog",
|
|
175
|
+
"@kidecms/core > @base-ui/react/input",
|
|
176
|
+
"@kidecms/core > @base-ui/react/merge-props",
|
|
177
|
+
"@kidecms/core > @base-ui/react/select",
|
|
178
|
+
"@kidecms/core > @base-ui/react/separator",
|
|
179
|
+
"@kidecms/core > @base-ui/react/tooltip",
|
|
180
|
+
"@kidecms/core > @base-ui/react/use-render",
|
|
181
|
+
"@kidecms/core > @tanstack/react-table",
|
|
182
|
+
"@kidecms/core > class-variance-authority",
|
|
183
|
+
"@kidecms/core > clsx",
|
|
184
|
+
"@kidecms/core > cmdk",
|
|
185
|
+
"@kidecms/core > lucide-react",
|
|
186
|
+
"@kidecms/core > tailwind-merge",
|
|
187
|
+
],
|
|
188
|
+
},
|
|
152
189
|
},
|
|
153
190
|
});
|
|
154
191
|
// Inject admin pages
|
package/dist/webhooks.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const MAX_RETRIES = 3;
|
|
2
|
+
const TIMEOUT_MS = 5000;
|
|
3
|
+
const RETRY_DELAYS = [1000, 3000, 9000];
|
|
4
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
async function deliverOnce(webhook, body) {
|
|
6
|
+
const controller = new AbortController();
|
|
7
|
+
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
8
|
+
try {
|
|
9
|
+
const response = await fetch(webhook.url, {
|
|
10
|
+
method: webhook.method ?? "POST",
|
|
11
|
+
headers: { "Content-Type": "application/json", ...webhook.headers },
|
|
12
|
+
body,
|
|
13
|
+
signal: controller.signal,
|
|
14
|
+
});
|
|
15
|
+
return { ok: response.ok, status: response.status };
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
clearTimeout(timeout);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function deliverWebhook(webhook, body) {
|
|
25
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
26
|
+
const result = await deliverOnce(webhook, body);
|
|
27
|
+
if (result.ok) {
|
|
28
|
+
if (attempt > 0) {
|
|
29
|
+
console.log(` [webhook] ${webhook.name} delivered after ${attempt + 1} attempts`);
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const detail = result.status ? `HTTP ${result.status}` : result.error;
|
|
34
|
+
if (attempt < MAX_RETRIES) {
|
|
35
|
+
console.warn(` [webhook] ${webhook.name} failed (${detail}) — retrying in ${RETRY_DELAYS[attempt]}ms`);
|
|
36
|
+
await sleep(RETRY_DELAYS[attempt]);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.error(` [webhook] ${webhook.name} failed permanently after ${MAX_RETRIES + 1} attempts: ${detail}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function dispatchWebhooks(config, event, collectionSlug, doc, user) {
|
|
44
|
+
const webhooks = config.admin?.webhooks;
|
|
45
|
+
if (!webhooks || webhooks.length === 0)
|
|
46
|
+
return;
|
|
47
|
+
const matching = webhooks.filter((webhook) => {
|
|
48
|
+
if (!webhook.events.includes(event))
|
|
49
|
+
return false;
|
|
50
|
+
if (webhook.collections && !webhook.collections.includes(collectionSlug))
|
|
51
|
+
return false;
|
|
52
|
+
return true;
|
|
53
|
+
});
|
|
54
|
+
if (matching.length === 0)
|
|
55
|
+
return;
|
|
56
|
+
const context = {
|
|
57
|
+
user: user ?? null,
|
|
58
|
+
event,
|
|
59
|
+
collection: collectionSlug,
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
// Fire all matching webhooks in parallel — don't block the operation
|
|
63
|
+
for (const webhook of matching) {
|
|
64
|
+
const payload = webhook.payload ? webhook.payload(doc, context) : { event, collection: collectionSlug, doc, user, timestamp: context.timestamp };
|
|
65
|
+
const body = JSON.stringify(payload);
|
|
66
|
+
// Fire and forget — failures are logged but don't bubble up
|
|
67
|
+
deliverWebhook(webhook, body).catch((err) => {
|
|
68
|
+
console.error(` [webhook] ${webhook.name} dispatcher error:`, err);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|