@caretcms/core 0.1.0 → 0.1.2
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/README.md +182 -6
- package/dist/browser-runtime.d.ts +16 -0
- package/dist/browser-runtime.d.ts.map +1 -1
- package/dist/browser-runtime.js +42 -23
- package/dist/browser-runtime.js.map +1 -1
- package/dist/contracts.d.ts +14 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +14 -0
- package/dist/contracts.js.map +1 -0
- package/dist/editable.d.ts +28 -0
- package/dist/editable.d.ts.map +1 -0
- package/dist/editable.js +122 -0
- package/dist/editable.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +274 -4
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +45 -3
- package/dist/loader.js.map +1 -1
- package/dist/providers/storage/markdown.d.ts +7 -0
- package/dist/providers/storage/markdown.d.ts.map +1 -0
- package/dist/providers/storage/markdown.js +6 -0
- package/dist/providers/storage/markdown.js.map +1 -0
- package/dist/runtime/auth/cookie-utils.d.ts +1 -0
- package/dist/runtime/auth/cookie-utils.d.ts.map +1 -1
- package/dist/runtime/auth/cookie-utils.js +1 -1
- package/dist/runtime/auth/cookie-utils.js.map +1 -1
- package/dist/runtime/auth/rate-limiter.d.ts.map +1 -1
- package/dist/runtime/auth/rate-limiter.js +19 -13
- package/dist/runtime/auth/rate-limiter.js.map +1 -1
- package/dist/runtime/auth/session.d.ts +8 -0
- package/dist/runtime/auth/session.d.ts.map +1 -1
- package/dist/runtime/auth/session.js +72 -11
- package/dist/runtime/auth/session.js.map +1 -1
- package/dist/runtime/config.d.ts +1 -0
- package/dist/runtime/config.d.ts.map +1 -1
- package/dist/runtime/config.js +2 -1
- package/dist/runtime/config.js.map +1 -1
- package/dist/runtime/git-journal.d.ts +19 -0
- package/dist/runtime/git-journal.d.ts.map +1 -0
- package/dist/runtime/git-journal.js +64 -0
- package/dist/runtime/git-journal.js.map +1 -0
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/middleware.d.ts.map +1 -1
- package/dist/runtime/middleware.js +125 -8
- package/dist/runtime/middleware.js.map +1 -1
- package/dist/runtime/mutations/contracts.d.ts.map +1 -1
- package/dist/runtime/mutations/contracts.js +7 -4
- package/dist/runtime/mutations/contracts.js.map +1 -1
- package/dist/runtime/mutations/engine.d.ts +7 -0
- package/dist/runtime/mutations/engine.d.ts.map +1 -1
- package/dist/runtime/mutations/engine.js +30 -17
- package/dist/runtime/mutations/engine.js.map +1 -1
- package/dist/runtime/providers.d.ts +16 -0
- package/dist/runtime/providers.d.ts.map +1 -1
- package/dist/runtime/providers.js +16 -0
- package/dist/runtime/providers.js.map +1 -1
- package/dist/runtime/publish.d.ts +40 -0
- package/dist/runtime/publish.d.ts.map +1 -0
- package/dist/runtime/publish.js +75 -0
- package/dist/runtime/publish.js.map +1 -0
- package/dist/runtime/rebuild-webhook.d.ts +29 -0
- package/dist/runtime/rebuild-webhook.d.ts.map +1 -0
- package/dist/runtime/rebuild-webhook.js +37 -0
- package/dist/runtime/rebuild-webhook.js.map +1 -0
- package/dist/runtime/request-context.d.ts +19 -0
- package/dist/runtime/request-context.d.ts.map +1 -1
- package/dist/runtime/request-context.js.map +1 -1
- package/dist/runtime/rewrite.d.ts +29 -1
- package/dist/runtime/rewrite.d.ts.map +1 -1
- package/dist/runtime/rewrite.js +30 -16
- package/dist/runtime/rewrite.js.map +1 -1
- package/dist/runtime/rich-allowlist.d.ts +29 -0
- package/dist/runtime/rich-allowlist.d.ts.map +1 -0
- package/dist/runtime/rich-allowlist.js +49 -0
- package/dist/runtime/rich-allowlist.js.map +1 -0
- package/dist/runtime/routes/_helpers.d.ts +33 -0
- package/dist/runtime/routes/_helpers.d.ts.map +1 -1
- package/dist/runtime/routes/_helpers.js +117 -1
- package/dist/runtime/routes/_helpers.js.map +1 -1
- package/dist/runtime/routes/admin-entry.d.ts +1 -0
- package/dist/runtime/routes/admin-entry.d.ts.map +1 -1
- package/dist/runtime/routes/admin-entry.js +3 -2
- package/dist/runtime/routes/admin-entry.js.map +1 -1
- package/dist/runtime/routes/admin.d.ts +1 -0
- package/dist/runtime/routes/admin.d.ts.map +1 -1
- package/dist/runtime/routes/admin.js +61 -9
- package/dist/runtime/routes/admin.js.map +1 -1
- package/dist/runtime/routes/auth-login.d.ts +1 -0
- package/dist/runtime/routes/auth-login.d.ts.map +1 -1
- package/dist/runtime/routes/auth-login.js +26 -8
- package/dist/runtime/routes/auth-login.js.map +1 -1
- package/dist/runtime/routes/auth-logout.d.ts +1 -0
- package/dist/runtime/routes/auth-logout.d.ts.map +1 -1
- package/dist/runtime/routes/auth-logout.js +5 -0
- package/dist/runtime/routes/auth-logout.js.map +1 -1
- package/dist/runtime/routes/auth-session.d.ts +1 -0
- package/dist/runtime/routes/auth-session.d.ts.map +1 -1
- package/dist/runtime/routes/auth-session.js +1 -0
- package/dist/runtime/routes/auth-session.js.map +1 -1
- package/dist/runtime/routes/collection-list.d.ts +1 -0
- package/dist/runtime/routes/collection-list.d.ts.map +1 -1
- package/dist/runtime/routes/collection-list.js +3 -2
- package/dist/runtime/routes/collection-list.js.map +1 -1
- package/dist/runtime/routes/collections-metadata.d.ts +1 -0
- package/dist/runtime/routes/collections-metadata.d.ts.map +1 -1
- package/dist/runtime/routes/collections-metadata.js +7 -4
- package/dist/runtime/routes/collections-metadata.js.map +1 -1
- package/dist/runtime/routes/draft.d.ts +13 -0
- package/dist/runtime/routes/draft.d.ts.map +1 -0
- package/dist/runtime/routes/draft.js +55 -0
- package/dist/runtime/routes/draft.js.map +1 -0
- package/dist/runtime/routes/editor-assets.d.ts +1 -0
- package/dist/runtime/routes/editor-assets.d.ts.map +1 -1
- package/dist/runtime/routes/editor-assets.js +1 -0
- package/dist/runtime/routes/editor-assets.js.map +1 -1
- package/dist/runtime/routes/entries.d.ts +1 -0
- package/dist/runtime/routes/entries.d.ts.map +1 -1
- package/dist/runtime/routes/entries.js +3 -2
- package/dist/runtime/routes/entries.js.map +1 -1
- package/dist/runtime/routes/history.d.ts +1 -0
- package/dist/runtime/routes/history.d.ts.map +1 -1
- package/dist/runtime/routes/history.js +24 -10
- package/dist/runtime/routes/history.js.map +1 -1
- package/dist/runtime/routes/mutate.d.ts +1 -0
- package/dist/runtime/routes/mutate.d.ts.map +1 -1
- package/dist/runtime/routes/mutate.js +7 -10
- package/dist/runtime/routes/mutate.js.map +1 -1
- package/dist/runtime/routes/publish.d.ts +10 -0
- package/dist/runtime/routes/publish.d.ts.map +1 -0
- package/dist/runtime/routes/publish.js +75 -0
- package/dist/runtime/routes/publish.js.map +1 -0
- package/dist/runtime/routes/schema.d.ts +1 -0
- package/dist/runtime/routes/schema.d.ts.map +1 -1
- package/dist/runtime/routes/schema.js +7 -4
- package/dist/runtime/routes/schema.js.map +1 -1
- package/dist/runtime/routes/studio-home.d.ts +1 -0
- package/dist/runtime/routes/studio-home.d.ts.map +1 -1
- package/dist/runtime/routes/studio-home.js +10 -9
- package/dist/runtime/routes/studio-home.js.map +1 -1
- package/dist/runtime/routes/theme-stylesheet.d.ts +1 -0
- package/dist/runtime/routes/theme-stylesheet.d.ts.map +1 -1
- package/dist/runtime/routes/theme-stylesheet.js +1 -0
- package/dist/runtime/routes/theme-stylesheet.js.map +1 -1
- package/dist/runtime/routes/upload.d.ts +1 -0
- package/dist/runtime/routes/upload.d.ts.map +1 -1
- package/dist/runtime/routes/upload.js +17 -3
- package/dist/runtime/routes/upload.js.map +1 -1
- package/dist/runtime/sanitize-html.d.ts +8 -2
- package/dist/runtime/sanitize-html.d.ts.map +1 -1
- package/dist/runtime/sanitize-html.js +57 -38
- package/dist/runtime/sanitize-html.js.map +1 -1
- package/dist/runtime/static-bake.d.ts +20 -0
- package/dist/runtime/static-bake.d.ts.map +1 -0
- package/dist/runtime/static-bake.js +58 -0
- package/dist/runtime/static-bake.js.map +1 -0
- package/dist/runtime/stega.d.ts +38 -0
- package/dist/runtime/stega.d.ts.map +1 -0
- package/dist/runtime/stega.js +98 -0
- package/dist/runtime/stega.js.map +1 -0
- package/dist/runtime/storage/atomic-write.d.ts +22 -0
- package/dist/runtime/storage/atomic-write.d.ts.map +1 -0
- package/dist/runtime/storage/atomic-write.js +44 -0
- package/dist/runtime/storage/atomic-write.js.map +1 -0
- package/dist/runtime/storage/filesystem-adapter.d.ts +14 -11
- package/dist/runtime/storage/filesystem-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/filesystem-adapter.js +71 -201
- package/dist/runtime/storage/filesystem-adapter.js.map +1 -1
- package/dist/runtime/storage/frontmatter-codec.d.ts +47 -0
- package/dist/runtime/storage/frontmatter-codec.d.ts.map +1 -0
- package/dist/runtime/storage/frontmatter-codec.js +523 -0
- package/dist/runtime/storage/frontmatter-codec.js.map +1 -0
- package/dist/runtime/storage/fs-runtime.d.ts +8 -0
- package/dist/runtime/storage/fs-runtime.d.ts.map +1 -0
- package/dist/runtime/storage/fs-runtime.js +40 -0
- package/dist/runtime/storage/fs-runtime.js.map +1 -0
- package/dist/runtime/storage/id-contracts.d.ts +22 -0
- package/dist/runtime/storage/id-contracts.d.ts.map +1 -0
- package/dist/runtime/storage/id-contracts.js +27 -0
- package/dist/runtime/storage/id-contracts.js.map +1 -0
- package/dist/runtime/storage/image-validation.d.ts +10 -0
- package/dist/runtime/storage/image-validation.d.ts.map +1 -1
- package/dist/runtime/storage/image-validation.js +17 -4
- package/dist/runtime/storage/image-validation.js.map +1 -1
- package/dist/runtime/storage/in-memory-adapter.d.ts +4 -0
- package/dist/runtime/storage/in-memory-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/in-memory-adapter.js +11 -0
- package/dist/runtime/storage/in-memory-adapter.js.map +1 -1
- package/dist/runtime/storage/markdown-adapter.d.ts +63 -0
- package/dist/runtime/storage/markdown-adapter.d.ts.map +1 -0
- package/dist/runtime/storage/markdown-adapter.js +227 -0
- package/dist/runtime/storage/markdown-adapter.js.map +1 -0
- package/dist/runtime/storage/quota-upload-handler.d.ts +2 -0
- package/dist/runtime/storage/quota-upload-handler.d.ts.map +1 -1
- package/dist/runtime/storage/quota-upload-handler.js +34 -8
- package/dist/runtime/storage/quota-upload-handler.js.map +1 -1
- package/dist/runtime/storage/session-overlay-adapter.d.ts +3 -0
- package/dist/runtime/storage/session-overlay-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/session-overlay-adapter.js +3 -1
- package/dist/runtime/storage/session-overlay-adapter.js.map +1 -1
- package/dist/runtime/storage/sidecar-meta-store.d.ts +51 -0
- package/dist/runtime/storage/sidecar-meta-store.d.ts.map +1 -0
- package/dist/runtime/storage/sidecar-meta-store.js +209 -0
- package/dist/runtime/storage/sidecar-meta-store.js.map +1 -0
- package/dist/runtime/themes/preset-tokens.d.ts +0 -1
- package/dist/runtime/themes/preset-tokens.d.ts.map +1 -1
- package/dist/runtime/themes/preset-tokens.js +1 -1
- package/dist/runtime/themes/preset-tokens.js.map +1 -1
- package/dist/runtime/utils.d.ts +9 -1
- package/dist/runtime/utils.d.ts.map +1 -1
- package/dist/runtime/utils.js +21 -1
- package/dist/runtime/utils.js.map +1 -1
- package/dist/runtime/views/studio-layout.d.ts.map +1 -1
- package/dist/runtime/views/studio-layout.js +11 -10
- package/dist/runtime/views/studio-layout.js.map +1 -1
- package/dist/schema-utils.d.ts.map +1 -1
- package/dist/schema-utils.js +1 -0
- package/dist/schema-utils.js.map +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -1
- package/static/cms/dev-toolbar/app.js +265 -0
- package/static/cms/editor/config.js +10 -0
- package/static/cms/editor/helpers.js +5 -1
- package/static/cms/editor/highlight.js +44 -0
- package/static/cms/editor/sanitize.js +56 -9
- package/static/cms/editor/stega-hydrate.js +80 -0
- package/static/cms/editor/text-edit.js +22 -0
- package/static/cms/editor/tokens-and-text.css +1 -1
- package/static/cms/editor/toolbar.css +101 -4
- package/static/cms/editor/toolbar.js +171 -21
- package/static/cms/editor.js +18 -1
- package/static/cms/signin-hint.css +74 -0
- package/static/cms/studio-login.css +70 -0
- package/static/cms/studio.css +80 -13
- package/dist/runtime/schema/template.d.ts +0 -15
- package/dist/runtime/schema/template.d.ts.map +0 -1
- package/dist/runtime/schema/template.js +0 -38
- package/dist/runtime/schema/template.js.map +0 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { withEntryLock } from "./mutations/engine.js";
|
|
2
|
+
import { TOMBSTONE_KEY } from "./storage/session-overlay-adapter.js";
|
|
3
|
+
function isTombstoneData(data) {
|
|
4
|
+
return Boolean(data && data[TOMBSTONE_KEY] === true);
|
|
5
|
+
}
|
|
6
|
+
/** The (collection, id) pairs that currently exist in the overlay within scope. */
|
|
7
|
+
async function overlayEntriesInScope(overlay, scope) {
|
|
8
|
+
const collections = scope.collection
|
|
9
|
+
? [scope.collection]
|
|
10
|
+
: await overlay.discoverCollections();
|
|
11
|
+
const out = [];
|
|
12
|
+
for (const collection of collections) {
|
|
13
|
+
const ids = scope.collection && scope.id
|
|
14
|
+
? [scope.id]
|
|
15
|
+
: await overlay.listEntryIds(collection);
|
|
16
|
+
for (const id of ids)
|
|
17
|
+
out.push({ collection, id });
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Flush the overlay into the base. A tombstoned draft entry deletes from the
|
|
23
|
+
* base; any other draft entry is written, the base revision bumped, and a
|
|
24
|
+
* `"publish"` history entry (carrying the pre-publish base data) appended. Each
|
|
25
|
+
* published entry is then cleared from the overlay so it stops shadowing the
|
|
26
|
+
* base. Returns the new base revisions so the client can reconcile its cached
|
|
27
|
+
* revision map — without this, the first edit after a publish would 409.
|
|
28
|
+
*
|
|
29
|
+
* NOT atomic across multiple entries (per-entry locks only): on a partial
|
|
30
|
+
* failure the returned list reflects exactly what was committed. Callers that
|
|
31
|
+
* surface "publish all" should report that list.
|
|
32
|
+
*/
|
|
33
|
+
export async function publishOverlay(base, overlay, scope = {}) {
|
|
34
|
+
const targets = await overlayEntriesInScope(overlay, scope);
|
|
35
|
+
const published = [];
|
|
36
|
+
for (const { collection, id } of targets) {
|
|
37
|
+
const result = await withEntryLock(collection, id, async () => {
|
|
38
|
+
const draft = await overlay.getEntry(collection, id);
|
|
39
|
+
if (!draft)
|
|
40
|
+
return null; // raced away; nothing to publish
|
|
41
|
+
if (isTombstoneData(draft.data)) {
|
|
42
|
+
await base.deleteEntry(collection, id);
|
|
43
|
+
await overlay.deleteEntry(collection, id);
|
|
44
|
+
return { collection, id, revision: await base.getRevision(collection, id), deleted: true };
|
|
45
|
+
}
|
|
46
|
+
const before = await base.getEntry(collection, id);
|
|
47
|
+
await base.writeEntry(collection, id, draft.data);
|
|
48
|
+
const revision = await base.bumpRevision(collection, id);
|
|
49
|
+
await base.appendHistory(collection, id, {
|
|
50
|
+
ts: Date.now(),
|
|
51
|
+
action: "publish",
|
|
52
|
+
data: before?.data ?? null,
|
|
53
|
+
});
|
|
54
|
+
await overlay.deleteEntry(collection, id);
|
|
55
|
+
return { collection, id, revision, deleted: false };
|
|
56
|
+
});
|
|
57
|
+
if (result)
|
|
58
|
+
published.push(result);
|
|
59
|
+
}
|
|
60
|
+
return published;
|
|
61
|
+
}
|
|
62
|
+
/** Count draft entries currently held in an editor overlay. */
|
|
63
|
+
export async function countOverlayDrafts(overlay, scope = {}) {
|
|
64
|
+
return (await overlayEntriesInScope(overlay, scope)).length;
|
|
65
|
+
}
|
|
66
|
+
/** Discard an editor's draft(s) within scope without touching the base. Returns
|
|
67
|
+
* the number of overlay entries cleared. */
|
|
68
|
+
export async function discardOverlay(overlay, scope = {}) {
|
|
69
|
+
const targets = await overlayEntriesInScope(overlay, scope);
|
|
70
|
+
for (const { collection, id } of targets) {
|
|
71
|
+
await withEntryLock(collection, id, () => overlay.deleteEntry(collection, id));
|
|
72
|
+
}
|
|
73
|
+
return targets.length;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=publish.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish.js","sourceRoot":"","sources":["../../src/runtime/publish.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface RebuildWebhookConfig {
|
|
2
|
+
webhookUrl: string | null;
|
|
3
|
+
method?: "POST" | "PUT";
|
|
4
|
+
headers?: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export interface RebuildWebhookPayload {
|
|
7
|
+
published: Array<{
|
|
8
|
+
collection: string;
|
|
9
|
+
id: string;
|
|
10
|
+
revision: number;
|
|
11
|
+
deleted: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
commit: string | null;
|
|
14
|
+
}
|
|
15
|
+
export type RebuildWebhookResult = {
|
|
16
|
+
triggered: false;
|
|
17
|
+
ok: true;
|
|
18
|
+
} | {
|
|
19
|
+
triggered: true;
|
|
20
|
+
ok: true;
|
|
21
|
+
status: number;
|
|
22
|
+
} | {
|
|
23
|
+
triggered: true;
|
|
24
|
+
ok: false;
|
|
25
|
+
status?: number;
|
|
26
|
+
error: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function triggerRebuildWebhook(config: RebuildWebhookConfig, payload: RebuildWebhookPayload): Promise<RebuildWebhookResult>;
|
|
29
|
+
//# sourceMappingURL=rebuild-webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rebuild-webhook.d.ts","sourceRoot":"","sources":["../../src/runtime/rebuild-webhook.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACzF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,SAAS,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,IAAI,CAAA;CAAE,GAC9B;IAAE,SAAS,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,SAAS,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAoC/B"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export async function triggerRebuildWebhook(config, payload) {
|
|
2
|
+
const webhookUrl = config.webhookUrl?.trim();
|
|
3
|
+
if (!webhookUrl)
|
|
4
|
+
return { triggered: false, ok: true };
|
|
5
|
+
try {
|
|
6
|
+
const response = await fetch(webhookUrl, {
|
|
7
|
+
method: config.method ?? "POST",
|
|
8
|
+
headers: {
|
|
9
|
+
"content-type": "application/json",
|
|
10
|
+
...(config.headers ?? {}),
|
|
11
|
+
},
|
|
12
|
+
body: JSON.stringify({
|
|
13
|
+
source: "caretcms",
|
|
14
|
+
event: "publish",
|
|
15
|
+
published: payload.published,
|
|
16
|
+
commit: payload.commit,
|
|
17
|
+
}),
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
return {
|
|
21
|
+
triggered: true,
|
|
22
|
+
ok: false,
|
|
23
|
+
status: response.status,
|
|
24
|
+
error: `Rebuild webhook failed with status ${response.status}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return { triggered: true, ok: true, status: response.status };
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return {
|
|
31
|
+
triggered: true,
|
|
32
|
+
ok: false,
|
|
33
|
+
error: error instanceof Error ? error.message : "Rebuild webhook failed",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=rebuild-webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rebuild-webhook.js","sourceRoot":"","sources":["../../src/runtime/rebuild-webhook.ts"],"names":[],"mappings":"AAgBA,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAA4B,EAC5B,OAA8B;IAE9B,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC;IAC7C,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAEvD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,UAAU,EAAE;YACvC,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,MAAM;YAC/B,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;aAC1B;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,MAAM,EAAE,UAAU;gBAClB,KAAK,EAAE,SAAS;gBAChB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,OAAO;gBACL,SAAS,EAAE,IAAI;gBACf,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,KAAK,EAAE,sCAAsC,QAAQ,CAAC,MAAM,EAAE;aAC/D,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;IAChE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,SAAS,EAAE,IAAI;YACf,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAwB;SACzE,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -4,6 +4,25 @@ export interface CaretRequestContext {
|
|
|
4
4
|
uploadHandler: UploadHandler;
|
|
5
5
|
sessionId: string | null;
|
|
6
6
|
demoMode: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* True when this request is from an authenticated editor (or an active demo
|
|
9
|
+
* overlay). Gates draft-only behavior such as stega encoding in the live
|
|
10
|
+
* loaders. Set by the middleware after auth is resolved; absent means false.
|
|
11
|
+
*/
|
|
12
|
+
editor?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The authenticated editor's id (from the session cookie), when present. Keys
|
|
15
|
+
* the per-editor draft overlay so unpublished edits stay isolated per editor.
|
|
16
|
+
* Absent for anonymous/public requests and legacy sessions.
|
|
17
|
+
*/
|
|
18
|
+
editorId?: string | null;
|
|
19
|
+
/**
|
|
20
|
+
* True only when a per-session storage overlay was actually installed for
|
|
21
|
+
* this demo request, guaranteeing writes are isolated from the shared base
|
|
22
|
+
* store. Demo editor rights are granted only when this holds — see
|
|
23
|
+
* `isEditorAuthenticated`. Absent/false means fail closed.
|
|
24
|
+
*/
|
|
25
|
+
overlayActive?: boolean;
|
|
7
26
|
}
|
|
8
27
|
export declare function runWithRequestContext<T>(context: CaretRequestContext, fn: () => Promise<T>): Promise<T>;
|
|
9
28
|
export declare function getRequestContext(): CaretRequestContext | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../src/runtime/request-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,cAAc,CAAC;IACxB,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../src/runtime/request-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,cAAc,CAAC;IACxB,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAID,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,OAAO,EAAE,mBAAmB,EAC5B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAEZ;AAED,wBAAgB,iBAAiB,IAAI,mBAAmB,GAAG,SAAS,CAEnE;AAED,wBAAgB,qBAAqB,IAAI,mBAAmB,CAQ3D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-context.js","sourceRoot":"","sources":["../../src/runtime/request-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"request-context.js","sourceRoot":"","sources":["../../src/runtime/request-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AA6BrD,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAuB,CAAC;AAE7D,MAAM,UAAU,qBAAqB,CACnC,OAA4B,EAC5B,EAAoB;IAEpB,OAAO,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,OAAO,CAAC,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC/B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,kIAAkI,CACnI,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -9,5 +9,33 @@
|
|
|
9
9
|
* Elements with nested child markup are skipped (editor warns at runtime).
|
|
10
10
|
*/
|
|
11
11
|
import type { StorageAdapter } from "../types.js";
|
|
12
|
-
|
|
12
|
+
type ScopeMatch = {
|
|
13
|
+
collection: string;
|
|
14
|
+
id: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Tags whose text content the rewrite engine can swap (`data-caret` bindings).
|
|
18
|
+
* `<img>` (src swap) is handled separately. This list is a PUBLIC CONTRACT:
|
|
19
|
+
* caretize mirrors it as its candidate set (a parity test asserts equality) —
|
|
20
|
+
* a `data-caret` on any tag outside this list saves through the editor but is
|
|
21
|
+
* never injected into the public render (a silently inert binding).
|
|
22
|
+
*/
|
|
23
|
+
export declare const REWRITABLE_TEXT_TAGS: readonly ["h1", "h2", "h3", "h4", "h5", "h6", "p", "span", "small", "strong", "em", "a", "li", "td", "th", "label", "button", "figcaption", "blockquote", "dt", "dd", "summary", "caption", "legend"];
|
|
24
|
+
/**
|
|
25
|
+
* CANONICAL data-caret value resolution (exported for the parser parity test):
|
|
26
|
+
* full triple as-is; bare field resolved against the nearest enclosing scope;
|
|
27
|
+
* anything else (2 or 4+ parts) is not a binding. The hand-mirrored copies in
|
|
28
|
+
* browser-runtime.ts, static/cms/editor/helpers.js, and
|
|
29
|
+
* static/cms/dev-toolbar/app.js are held to this behavior by
|
|
30
|
+
* tests/unit/caret-parser-parity.test.ts.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveBinding(caretValue: string, scope: ScopeMatch | null): {
|
|
33
|
+
collection: string;
|
|
34
|
+
id: string;
|
|
35
|
+
field: string;
|
|
36
|
+
} | null;
|
|
37
|
+
export declare function rewriteCaretAttributes(html: string, adapter: StorageAdapter, options?: {
|
|
38
|
+
allowedClasses?: Record<string, readonly string[]>;
|
|
39
|
+
}): Promise<string>;
|
|
40
|
+
export {};
|
|
13
41
|
//# sourceMappingURL=rewrite.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rewrite.d.ts","sourceRoot":"","sources":["../../src/runtime/rewrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAa,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"rewrite.d.ts","sourceRoot":"","sources":["../../src/runtime/rewrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAa,MAAM,aAAa,CAAC;AAkB7D,KAAK,UAAU,GAAG;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AASF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,uMAKvB,CAAC;AAwGX;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,UAAU,GAAG,IAAI,GACvB;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAW1D;AAmCD,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAA;CAAE,GAC/D,OAAO,CAAC,MAAM,CAAC,CAyHjB"}
|
package/dist/runtime/rewrite.js
CHANGED
|
@@ -9,7 +9,21 @@
|
|
|
9
9
|
* Elements with nested child markup are skipped (editor warns at runtime).
|
|
10
10
|
*/
|
|
11
11
|
import { sanitizeHtml } from "./sanitize-html.js";
|
|
12
|
+
import { getNestedValue } from "./utils.js";
|
|
12
13
|
// --- Patterns ---
|
|
14
|
+
/**
|
|
15
|
+
* Tags whose text content the rewrite engine can swap (`data-caret` bindings).
|
|
16
|
+
* `<img>` (src swap) is handled separately. This list is a PUBLIC CONTRACT:
|
|
17
|
+
* caretize mirrors it as its candidate set (a parity test asserts equality) —
|
|
18
|
+
* a `data-caret` on any tag outside this list saves through the editor but is
|
|
19
|
+
* never injected into the public render (a silently inert binding).
|
|
20
|
+
*/
|
|
21
|
+
export const REWRITABLE_TEXT_TAGS = [
|
|
22
|
+
"h1", "h2", "h3", "h4", "h5", "h6",
|
|
23
|
+
"p", "span", "small", "strong", "em", "a", "li", "td", "th", "label",
|
|
24
|
+
"button", "figcaption", "blockquote", "dt", "dd", "summary", "caption",
|
|
25
|
+
"legend",
|
|
26
|
+
];
|
|
13
27
|
// Matches any element with data-caret attribute.
|
|
14
28
|
// Captures: tag name, full opening tag, data-caret value, content between tags, closing tag
|
|
15
29
|
//
|
|
@@ -19,7 +33,7 @@ import { sanitizeHtml } from "./sanitize-html.js";
|
|
|
19
33
|
// capture the inner content. <img> is handled separately by IMG_ELEMENT_PATTERN
|
|
20
34
|
// because it is a void element with no closing tag and (in standard HTML5
|
|
21
35
|
// output) no self-closing slash — it would never match this pattern.
|
|
22
|
-
const CARET_ELEMENT_PATTERN =
|
|
36
|
+
const CARET_ELEMENT_PATTERN = new RegExp(`<(${REWRITABLE_TEXT_TAGS.join("|")})\\b([^>]*?\\bdata-caret\\s*=\\s*"([^"]*)"[^>]*?)(?:\\/>|>([\\s\\S]*?)<\\/\\1>)`);
|
|
23
37
|
// Void <img> with a data-caret binding. Closes on a bare `>` or a self-closing
|
|
24
38
|
// `/>` — never on a closing tag. Captures: 1 = attribute string, 2 = data-caret value.
|
|
25
39
|
const IMG_ELEMENT_PATTERN = /<img\b([^>]*?\bdata-caret\s*=\s*"([^"]*)"[^>]*?)\/?>/;
|
|
@@ -93,7 +107,15 @@ function findNearestScope(stack) {
|
|
|
93
107
|
}
|
|
94
108
|
return null;
|
|
95
109
|
}
|
|
96
|
-
|
|
110
|
+
/**
|
|
111
|
+
* CANONICAL data-caret value resolution (exported for the parser parity test):
|
|
112
|
+
* full triple as-is; bare field resolved against the nearest enclosing scope;
|
|
113
|
+
* anything else (2 or 4+ parts) is not a binding. The hand-mirrored copies in
|
|
114
|
+
* browser-runtime.ts, static/cms/editor/helpers.js, and
|
|
115
|
+
* static/cms/dev-toolbar/app.js are held to this behavior by
|
|
116
|
+
* tests/unit/caret-parser-parity.test.ts.
|
|
117
|
+
*/
|
|
118
|
+
export function resolveBinding(caretValue, scope) {
|
|
97
119
|
const parts = caretValue.split("::");
|
|
98
120
|
if (parts.length === 3) {
|
|
99
121
|
return { collection: parts[0], id: parts[1], field: parts[2] };
|
|
@@ -123,20 +145,12 @@ async function batchLoadEntries(adapter, keys) {
|
|
|
123
145
|
await Promise.all(promises);
|
|
124
146
|
return entries;
|
|
125
147
|
}
|
|
126
|
-
function
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
for (const key of keys) {
|
|
130
|
-
if (current === null || current === undefined)
|
|
131
|
-
return undefined;
|
|
132
|
-
if (typeof current !== "object")
|
|
133
|
-
return undefined;
|
|
134
|
-
current = current[key];
|
|
135
|
-
}
|
|
136
|
-
return typeof current === "string" ? current : undefined;
|
|
148
|
+
function getNestedString(data, path) {
|
|
149
|
+
const value = getNestedValue(data, path);
|
|
150
|
+
return typeof value === "string" ? value : undefined;
|
|
137
151
|
}
|
|
138
152
|
// --- Main rewrite ---
|
|
139
|
-
export async function rewriteCaretAttributes(html, adapter) {
|
|
153
|
+
export async function rewriteCaretAttributes(html, adapter, options) {
|
|
140
154
|
// 1. Find all data-caret bindings and collect unique entry keys
|
|
141
155
|
const bindings = [];
|
|
142
156
|
const entryKeys = new Set();
|
|
@@ -193,7 +207,7 @@ export async function rewriteCaretAttributes(html, adapter) {
|
|
|
193
207
|
const entry = entries.get(entryKey);
|
|
194
208
|
if (!entry)
|
|
195
209
|
continue;
|
|
196
|
-
const storedValue =
|
|
210
|
+
const storedValue = getNestedString(entry.data, binding.field);
|
|
197
211
|
if (storedValue === undefined)
|
|
198
212
|
continue;
|
|
199
213
|
if (binding.isImg) {
|
|
@@ -224,7 +238,7 @@ export async function rewriteCaretAttributes(html, adapter) {
|
|
|
224
238
|
const before = binding.fullMatch.slice(0, openTagEnd);
|
|
225
239
|
const after = binding.fullMatch.slice(closeTagStart);
|
|
226
240
|
const injected = binding.isRich
|
|
227
|
-
? sanitizeHtml(storedValue)
|
|
241
|
+
? sanitizeHtml(storedValue, { allowedClasses: options?.allowedClasses })
|
|
228
242
|
: escapeHtml(storedValue);
|
|
229
243
|
const newMatch = before + injected + after;
|
|
230
244
|
result =
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rewrite.js","sourceRoot":"","sources":["../../src/runtime/rewrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"rewrite.js","sourceRoot":"","sources":["../../src/runtime/rewrite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared rich-text allowlist + class matcher.
|
|
3
|
+
*
|
|
4
|
+
* The single source of truth for what the rich-text sanitizers keep. Imported
|
|
5
|
+
* by the two TypeScript sanitizers — the server render path (sanitize-html.ts,
|
|
6
|
+
* regex-based, no DOM) and the cloud live-sync path (browser-runtime.ts, DOM).
|
|
7
|
+
*
|
|
8
|
+
* The editor-save sanitizer in static/cms/editor/sanitize.js is shipped as raw,
|
|
9
|
+
* unbundled JS, so it can't import this module — it MUST mirror these values
|
|
10
|
+
* and the classAllowed() logic by hand. That hand mirror is held in lockstep by
|
|
11
|
+
* tests/unit/contracts-parity.test.ts ("browser sanitizer parity"), which fails
|
|
12
|
+
* CI on any drift (W0). Keep all three sanitizers in lockstep.
|
|
13
|
+
*/
|
|
14
|
+
export declare const RICH_ALLOWED_TAGS: Set<string>;
|
|
15
|
+
/** Attributes preserved per tag (class is gated separately, by allowedClasses). */
|
|
16
|
+
export declare const RICH_ALLOWED_ATTRS: Record<string, Set<string>>;
|
|
17
|
+
export declare const SAFE_HREF_RE: RegExp;
|
|
18
|
+
/**
|
|
19
|
+
* Per-tag class allowlist. Keys are tag names, values are allowed class names.
|
|
20
|
+
* A pattern ending in `*` is a prefix match (`text-*` allows `text-primary`);
|
|
21
|
+
* a lone `*` allows any class on that tag. Anything not matched is dropped.
|
|
22
|
+
* Mirrors the `allowedClasses` option of the `sanitize-html` package.
|
|
23
|
+
*/
|
|
24
|
+
export type AllowedClasses = Record<string, readonly string[]>;
|
|
25
|
+
/** Does `cls` match any pattern in `patterns`? (exact, `prefix-*`, or lone `*`) */
|
|
26
|
+
export declare function classAllowed(cls: string, patterns: readonly string[]): boolean;
|
|
27
|
+
/** Filter a raw class attribute value to the tag's allowed patterns. */
|
|
28
|
+
export declare function filterClasses(value: string, patterns: readonly string[]): string;
|
|
29
|
+
//# sourceMappingURL=rich-allowlist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rich-allowlist.d.ts","sourceRoot":"","sources":["../../src/runtime/rich-allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,eAAO,MAAM,iBAAiB,aAE5B,CAAC;AAEH,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAE1D,CAAC;AAEF,eAAO,MAAM,YAAY,QAAkC,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;AAE/D,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAU9E;AAED,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAKhF"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared rich-text allowlist + class matcher.
|
|
3
|
+
*
|
|
4
|
+
* The single source of truth for what the rich-text sanitizers keep. Imported
|
|
5
|
+
* by the two TypeScript sanitizers — the server render path (sanitize-html.ts,
|
|
6
|
+
* regex-based, no DOM) and the cloud live-sync path (browser-runtime.ts, DOM).
|
|
7
|
+
*
|
|
8
|
+
* The editor-save sanitizer in static/cms/editor/sanitize.js is shipped as raw,
|
|
9
|
+
* unbundled JS, so it can't import this module — it MUST mirror these values
|
|
10
|
+
* and the classAllowed() logic by hand. That hand mirror is held in lockstep by
|
|
11
|
+
* tests/unit/contracts-parity.test.ts ("browser sanitizer parity"), which fails
|
|
12
|
+
* CI on any drift (W0). Keep all three sanitizers in lockstep.
|
|
13
|
+
*/
|
|
14
|
+
// `span` (W4) lets styled inline runs (`<span class="gold">…</span>`, common in
|
|
15
|
+
// headings) survive in rich content — but it carries NO attributes of its own
|
|
16
|
+
// (see RICH_ALLOWED_ATTRS): its `class` is kept only when blessed via the per-tag
|
|
17
|
+
// `allowedClasses` config, and everything else (incl. on*=, style=) is stripped.
|
|
18
|
+
// Fuzzed in tests/unit/sanitize-html-properties.test.ts.
|
|
19
|
+
export const RICH_ALLOWED_TAGS = new Set([
|
|
20
|
+
"b", "strong", "i", "em", "u", "s", "a", "br", "sub", "sup", "span",
|
|
21
|
+
]);
|
|
22
|
+
/** Attributes preserved per tag (class is gated separately, by allowedClasses). */
|
|
23
|
+
export const RICH_ALLOWED_ATTRS = {
|
|
24
|
+
a: new Set(["href", "target", "rel"]),
|
|
25
|
+
};
|
|
26
|
+
export const SAFE_HREF_RE = /^(?:https?:|mailto:|tel:|\/)/i;
|
|
27
|
+
/** Does `cls` match any pattern in `patterns`? (exact, `prefix-*`, or lone `*`) */
|
|
28
|
+
export function classAllowed(cls, patterns) {
|
|
29
|
+
for (const p of patterns) {
|
|
30
|
+
if (p === "*")
|
|
31
|
+
return true;
|
|
32
|
+
if (p.endsWith("*")) {
|
|
33
|
+
if (cls.startsWith(p.slice(0, -1)))
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
else if (cls === p) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/** Filter a raw class attribute value to the tag's allowed patterns. */
|
|
43
|
+
export function filterClasses(value, patterns) {
|
|
44
|
+
return value
|
|
45
|
+
.split(/\s+/)
|
|
46
|
+
.filter((c) => c !== "" && classAllowed(c, patterns))
|
|
47
|
+
.join(" ");
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=rich-allowlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rich-allowlist.js","sourceRoot":"","sources":["../../src/runtime/rich-allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,gFAAgF;AAChF,8EAA8E;AAC9E,kFAAkF;AAClF,iFAAiF;AACjF,yDAAyD;AACzD,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IACvC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM;CACpE,CAAC,CAAC;AAEH,mFAAmF;AACnF,MAAM,CAAC,MAAM,kBAAkB,GAAgC;IAC7D,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,+BAA+B,CAAC;AAU5D,mFAAmF;AACnF,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,QAA2B;IACnE,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAC3B,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC;QAClD,CAAC;aAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,QAA2B;IACtE,OAAO,KAAK;SACT,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;SACpD,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
import type { StorageAdapter, UploadHandler } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Storage adapter for CMS API/Studio routes. Prefer the middleware ALS scope;
|
|
4
|
+
* fall back to the configured base adapter when injectRoute handlers run
|
|
5
|
+
* outside middleware (seen in Astro static dev after HMR or stale processes).
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveAdapter(): Promise<StorageAdapter>;
|
|
8
|
+
export declare function resolveUploadHandler(): Promise<UploadHandler>;
|
|
2
9
|
export declare function json(body: Record<string, unknown>, status?: number): Response;
|
|
10
|
+
/** Default cap for JSON command payloads (mutate/history/login). */
|
|
11
|
+
export declare const MAX_JSON_BODY_BYTES: number;
|
|
12
|
+
/** Default cap for multipart upload bodies before they're buffered. */
|
|
13
|
+
export declare const MAX_UPLOAD_BODY_BYTES: number;
|
|
14
|
+
/**
|
|
15
|
+
* Reject a request whose declared Content-Length exceeds `maxBytes`. Cheap
|
|
16
|
+
* first line of defense for bodies we must hand to a buffering parser (e.g.
|
|
17
|
+
* formData()): an honest client and most attacks announce their size. Returns
|
|
18
|
+
* a 413 response when over the cap, otherwise null. Absent/garbage
|
|
19
|
+
* Content-Length passes here — pair with a streaming cap where possible.
|
|
20
|
+
*/
|
|
21
|
+
export declare function enforceContentLength(request: Request, maxBytes: number): Response | null;
|
|
22
|
+
type JsonBodyResult = {
|
|
23
|
+
ok: true;
|
|
24
|
+
value: unknown;
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
response: Response;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Read and JSON-parse a request body with a hard byte cap, streaming the body
|
|
31
|
+
* and aborting once the running total exceeds `maxBytes` so an attacker can't
|
|
32
|
+
* exhaust memory by omitting Content-Length and sending a chunked flood.
|
|
33
|
+
*/
|
|
34
|
+
export declare function readJsonBody(request: Request, maxBytes?: number): Promise<JsonBodyResult>;
|
|
3
35
|
/**
|
|
4
36
|
* Lightweight CSRF defense for mutating endpoints.
|
|
5
37
|
*
|
|
@@ -14,4 +46,5 @@ export declare function enforceCsrfHeader(request: Request): Response | null;
|
|
|
14
46
|
export declare function getAdapter(): StorageAdapter;
|
|
15
47
|
export declare function getUploadHandler(): UploadHandler;
|
|
16
48
|
export declare function getSessionId(): string | null;
|
|
49
|
+
export {};
|
|
17
50
|
//# sourceMappingURL=_helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_helpers.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"_helpers.d.ts","sourceRoot":"","sources":["../../../src/runtime/routes/_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAyBpE;;;;GAIG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,cAAc,CAAC,CAI9D;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,aAAa,CAAC,CAInE;AAKD,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,SAAM,GAAG,QAAQ,CAK1E;AAED,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,QAAc,CAAC;AAC/C,uEAAuE;AACvE,eAAO,MAAM,qBAAqB,QAAmB,CAAC;AAEtD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,GACf,QAAQ,GAAG,IAAI,CAMjB;AAED,KAAK,cAAc,GACf;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC5B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEtC;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,OAAO,EAChB,QAAQ,GAAE,MAA4B,GACrC,OAAO,CAAC,cAAc,CAAC,CAmBzB;AA2CD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAWnE;AAED,wBAAgB,UAAU,IAAI,cAAc,CAE3C;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD;AAED,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C"}
|
|
@@ -1,4 +1,37 @@
|
|
|
1
|
-
import { requireRequestContext } from "../request-context.js";
|
|
1
|
+
import { getRequestContext, requireRequestContext, } from "../request-context.js";
|
|
2
|
+
import { getRuntimeServices } from "../providers.js";
|
|
3
|
+
let bootstrapContext = null;
|
|
4
|
+
async function bootstrapRequestContext() {
|
|
5
|
+
if (!bootstrapContext) {
|
|
6
|
+
bootstrapContext = getRuntimeServices().then((services) => ({
|
|
7
|
+
adapter: services.adapter,
|
|
8
|
+
uploadHandler: services.uploadHandler,
|
|
9
|
+
sessionId: null,
|
|
10
|
+
editorId: null,
|
|
11
|
+
demoMode: false,
|
|
12
|
+
overlayActive: false,
|
|
13
|
+
editor: false,
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
return bootstrapContext;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Storage adapter for CMS API/Studio routes. Prefer the middleware ALS scope;
|
|
20
|
+
* fall back to the configured base adapter when injectRoute handlers run
|
|
21
|
+
* outside middleware (seen in Astro static dev after HMR or stale processes).
|
|
22
|
+
*/
|
|
23
|
+
export async function resolveAdapter() {
|
|
24
|
+
const ctx = getRequestContext();
|
|
25
|
+
if (ctx)
|
|
26
|
+
return ctx.adapter;
|
|
27
|
+
return (await bootstrapRequestContext()).adapter;
|
|
28
|
+
}
|
|
29
|
+
export async function resolveUploadHandler() {
|
|
30
|
+
const ctx = getRequestContext();
|
|
31
|
+
if (ctx)
|
|
32
|
+
return ctx.uploadHandler;
|
|
33
|
+
return (await bootstrapRequestContext()).uploadHandler;
|
|
34
|
+
}
|
|
2
35
|
const CSRF_HEADER = "x-caret-request";
|
|
3
36
|
const CSRF_HEADER_VALUE = "1";
|
|
4
37
|
export function json(body, status = 200) {
|
|
@@ -7,6 +40,89 @@ export function json(body, status = 200) {
|
|
|
7
40
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
8
41
|
});
|
|
9
42
|
}
|
|
43
|
+
/** Default cap for JSON command payloads (mutate/history/login). */
|
|
44
|
+
export const MAX_JSON_BODY_BYTES = 1024 * 1024; // 1 MB
|
|
45
|
+
/** Default cap for multipart upload bodies before they're buffered. */
|
|
46
|
+
export const MAX_UPLOAD_BODY_BYTES = 25 * 1024 * 1024; // 25 MB
|
|
47
|
+
/**
|
|
48
|
+
* Reject a request whose declared Content-Length exceeds `maxBytes`. Cheap
|
|
49
|
+
* first line of defense for bodies we must hand to a buffering parser (e.g.
|
|
50
|
+
* formData()): an honest client and most attacks announce their size. Returns
|
|
51
|
+
* a 413 response when over the cap, otherwise null. Absent/garbage
|
|
52
|
+
* Content-Length passes here — pair with a streaming cap where possible.
|
|
53
|
+
*/
|
|
54
|
+
export function enforceContentLength(request, maxBytes) {
|
|
55
|
+
const declared = Number(request.headers.get("content-length") ?? "");
|
|
56
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
57
|
+
return json({ error: "Payload too large" }, 413);
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Read and JSON-parse a request body with a hard byte cap, streaming the body
|
|
63
|
+
* and aborting once the running total exceeds `maxBytes` so an attacker can't
|
|
64
|
+
* exhaust memory by omitting Content-Length and sending a chunked flood.
|
|
65
|
+
*/
|
|
66
|
+
export async function readJsonBody(request, maxBytes = MAX_JSON_BODY_BYTES) {
|
|
67
|
+
const tooLarge = enforceContentLength(request, maxBytes);
|
|
68
|
+
if (tooLarge)
|
|
69
|
+
return { ok: false, response: tooLarge };
|
|
70
|
+
let text;
|
|
71
|
+
try {
|
|
72
|
+
text = await readBodyTextCapped(request, maxBytes);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof BodyTooLargeError) {
|
|
76
|
+
return { ok: false, response: json({ error: "Payload too large" }, 413) };
|
|
77
|
+
}
|
|
78
|
+
return { ok: false, response: json({ error: "Invalid body" }, 400) };
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
return { ok: true, value: JSON.parse(text) };
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return { ok: false, response: json({ error: "Invalid JSON" }, 400) };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
class BodyTooLargeError extends Error {
|
|
88
|
+
}
|
|
89
|
+
async function readBodyTextCapped(request, maxBytes) {
|
|
90
|
+
const body = request.body;
|
|
91
|
+
if (!body) {
|
|
92
|
+
const text = await request.text();
|
|
93
|
+
if (text.length > maxBytes)
|
|
94
|
+
throw new BodyTooLargeError();
|
|
95
|
+
return text;
|
|
96
|
+
}
|
|
97
|
+
const reader = body.getReader();
|
|
98
|
+
const chunks = [];
|
|
99
|
+
let total = 0;
|
|
100
|
+
try {
|
|
101
|
+
for (;;) {
|
|
102
|
+
const { done, value } = await reader.read();
|
|
103
|
+
if (done)
|
|
104
|
+
break;
|
|
105
|
+
if (!value)
|
|
106
|
+
continue;
|
|
107
|
+
total += value.byteLength;
|
|
108
|
+
if (total > maxBytes) {
|
|
109
|
+
await reader.cancel().catch(() => { });
|
|
110
|
+
throw new BodyTooLargeError();
|
|
111
|
+
}
|
|
112
|
+
chunks.push(value);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
reader.releaseLock();
|
|
117
|
+
}
|
|
118
|
+
const merged = new Uint8Array(total);
|
|
119
|
+
let offset = 0;
|
|
120
|
+
for (const chunk of chunks) {
|
|
121
|
+
merged.set(chunk, offset);
|
|
122
|
+
offset += chunk.byteLength;
|
|
123
|
+
}
|
|
124
|
+
return new TextDecoder().decode(merged);
|
|
125
|
+
}
|
|
10
126
|
/**
|
|
11
127
|
* Lightweight CSRF defense for mutating endpoints.
|
|
12
128
|
*
|