@aeptus/aexeo-emdash 0.1.2 → 0.2.0
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/CHANGELOG.md +55 -1
- package/dist/adapter.d.ts +12 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/admin/Findings.d.ts +3 -0
- package/dist/admin/Findings.d.ts.map +1 -0
- package/dist/admin.d.ts +5 -0
- package/dist/admin.d.ts.map +1 -0
- package/dist/admin.js +214 -0
- package/dist/configured.d.ts.map +1 -1
- package/dist/configured.js +163 -10
- package/dist/data-route.d.ts +48 -0
- package/dist/data-route.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/plugin.d.ts +11 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/sandbox-entry.js +47 -8
- package/package.json +13 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,59 @@ and the project follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.0] - 2026-04-28
|
|
10
|
+
|
|
11
|
+
Adds the long-asked clickable routes on the findings page. Block
|
|
12
|
+
Kit can't render external links in any element type, so /findings
|
|
13
|
+
now ships as a React component registered via the plugin's
|
|
14
|
+
`adminEntry`. /document and the dashboard widget continue to use
|
|
15
|
+
Block Kit (no link needs there).
|
|
16
|
+
|
|
17
|
+
**Compatibility:** verified against emdash `0.7.0` and `0.8.0`.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **Clickable routes on /findings.** Each row's route links to the
|
|
22
|
+
emdash edit URL for that document
|
|
23
|
+
(`/_emdash/admin/content/<collection>/<id>`). Published documents
|
|
24
|
+
also get a "live ↗" link to the public URL
|
|
25
|
+
(`<emdash:site_url><route>`). Drafts surface their status
|
|
26
|
+
(`draft`, `scheduled`, etc.) inline so editorial state is visible
|
|
27
|
+
at a glance.
|
|
28
|
+
- **`adminEntry` field** on the configured-mode plugin descriptor.
|
|
29
|
+
Points at `@aeptus/aexeo-emdash/admin`, which exports `pages`
|
|
30
|
+
registering the `<Findings/>` React component for `/findings`.
|
|
31
|
+
emdash's `usePluginPage` hook picks this up and renders the
|
|
32
|
+
React component in place of the Block Kit fallback.
|
|
33
|
+
- **Two new HTTP routes** the React component consumes:
|
|
34
|
+
- `POST /_emdash/api/plugins/aexeo-seogeo/data` — read current
|
|
35
|
+
findings + per-route metadata + computed URLs (no
|
|
36
|
+
re-evaluation).
|
|
37
|
+
- `POST /_emdash/api/plugins/aexeo-seogeo/refresh` — sweep the
|
|
38
|
+
configured collections, write findings to KV, return the same
|
|
39
|
+
payload as `/data` plus a refresh summary.
|
|
40
|
+
Both routes return JSON in the shape exported as `FindingsPayload`
|
|
41
|
+
from `dist/data-route.d.ts`. The shape is the wire contract
|
|
42
|
+
between plugin and admin component and is intentionally
|
|
43
|
+
additive — fields will be appended in future versions, never
|
|
44
|
+
renamed or removed.
|
|
45
|
+
- `react` declared as an optional peer dependency
|
|
46
|
+
(`^18.0.0 || ^19.0.0`). Optional because plugins that don't use
|
|
47
|
+
the React findings page (e.g. ones that override `adminEntry`)
|
|
48
|
+
don't need it; emdash hosts that DO render adminEntry components
|
|
49
|
+
always have React installed already.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- `documentKey(route)` KV entries now store
|
|
54
|
+
`{ document, meta: { id, collection, status, slug, title } }`
|
|
55
|
+
instead of the bare `EmdashDocument`. The new metadata is what
|
|
56
|
+
the React component reads to construct edit / public URLs without
|
|
57
|
+
an extra DB round-trip per row. Pre-0.2.0 entries are
|
|
58
|
+
backwards-compatible — `readAllStoredDocuments` synthesizes a
|
|
59
|
+
minimal meta blob for legacy entries so a single corrupted-shape
|
|
60
|
+
row doesn't break the page.
|
|
61
|
+
|
|
9
62
|
## [0.1.2] - 2026-04-28
|
|
10
63
|
|
|
11
64
|
Functional patch. Three bugs flagged by the aeptus web team after
|
|
@@ -100,7 +153,8 @@ First public release.
|
|
|
100
153
|
`astro.config.mjs` for the WASM import to resolve to a
|
|
101
154
|
precompiled `WebAssembly.Module`. Not optional.
|
|
102
155
|
|
|
103
|
-
[Unreleased]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.
|
|
156
|
+
[Unreleased]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.2.0...HEAD
|
|
157
|
+
[0.2.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.2...aexeo-emdash-v0.2.0
|
|
104
158
|
[0.1.2]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.1...aexeo-emdash-v0.1.2
|
|
105
159
|
[0.1.1]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.0...aexeo-emdash-v0.1.1
|
|
106
160
|
[0.1.0]: https://github.com/schiste/Aexeo/releases/tag/aexeo-emdash-v0.1.0
|
package/dist/adapter.d.ts
CHANGED
|
@@ -12,5 +12,17 @@ export interface EmdashContentItem {
|
|
|
12
12
|
canonical?: string | null;
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
+
export interface EmdashContentMeta {
|
|
16
|
+
id: string;
|
|
17
|
+
collection: string;
|
|
18
|
+
slug: string | null;
|
|
19
|
+
status: string;
|
|
20
|
+
title: string;
|
|
21
|
+
}
|
|
22
|
+
export interface AdaptedContent {
|
|
23
|
+
document: EmdashDocument;
|
|
24
|
+
meta: EmdashContentMeta;
|
|
25
|
+
}
|
|
15
26
|
export declare function contentItemToEmdashDocument(content: EmdashContentItem): EmdashDocument;
|
|
27
|
+
export declare function adaptContentItem(content: EmdashContentItem): AdaptedContent;
|
|
16
28
|
//# sourceMappingURL=adapter.d.ts.map
|
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAC;AAmBpE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,iBAAiB,GACzB,cAAc,
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAC;AAmBpE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAOD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IAIf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,iBAAiB,GACzB,cAAc,CAEhB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAkC3E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Findings.d.ts","sourceRoot":"","sources":["../../src/admin/Findings.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAa/B,wBAAgB,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CA2L5C"}
|
package/dist/admin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C,eAAO,MAAM,KAAK;;CAEjB,CAAC"}
|
package/dist/admin.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// src/admin/Findings.tsx
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
var PLUGIN_ID = "aexeo-seogeo";
|
|
5
|
+
var API_BASE = "/_emdash/api";
|
|
6
|
+
function Findings() {
|
|
7
|
+
const [state, setState] = useState({
|
|
8
|
+
loading: true,
|
|
9
|
+
error: null,
|
|
10
|
+
payload: null
|
|
11
|
+
});
|
|
12
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
13
|
+
const [toast, setToast] = useState(null);
|
|
14
|
+
const load = useCallback(async () => {
|
|
15
|
+
setState((s) => ({ ...s, loading: true, error: null }));
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch(`${API_BASE}/plugins/${PLUGIN_ID}/data`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: csrfHeaders(),
|
|
20
|
+
body: "{}"
|
|
21
|
+
});
|
|
22
|
+
const body = await res.json();
|
|
23
|
+
const payload2 = unwrap(body, res);
|
|
24
|
+
setState({ loading: false, error: null, payload: payload2 });
|
|
25
|
+
} catch (err) {
|
|
26
|
+
setState({
|
|
27
|
+
loading: false,
|
|
28
|
+
error: err instanceof Error ? err.message : String(err),
|
|
29
|
+
payload: null
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}, []);
|
|
33
|
+
const refresh = useCallback(async () => {
|
|
34
|
+
setRefreshing(true);
|
|
35
|
+
setToast(null);
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch(`${API_BASE}/plugins/${PLUGIN_ID}/refresh`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: csrfHeaders(),
|
|
40
|
+
body: "{}"
|
|
41
|
+
});
|
|
42
|
+
const body = await res.json();
|
|
43
|
+
const data = unwrap(body, res);
|
|
44
|
+
setState({ loading: false, error: null, payload: data.payload });
|
|
45
|
+
if (data.summary) {
|
|
46
|
+
const { documentsScanned, routesUpdated, totalFindings, errors } = data.summary;
|
|
47
|
+
if (errors.length > 0) {
|
|
48
|
+
setToast(`Refresh completed with ${errors.length} errors: ${errors.join(" \u2022 ")}`);
|
|
49
|
+
} else {
|
|
50
|
+
setToast(
|
|
51
|
+
`Refreshed ${routesUpdated} routes (${totalFindings} findings across ${documentsScanned} documents)`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
setToast(
|
|
57
|
+
`Refresh failed: ${err instanceof Error ? err.message : String(err)}`
|
|
58
|
+
);
|
|
59
|
+
} finally {
|
|
60
|
+
setRefreshing(false);
|
|
61
|
+
}
|
|
62
|
+
}, []);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
void load();
|
|
65
|
+
}, [load]);
|
|
66
|
+
if (state.loading && state.payload === null) {
|
|
67
|
+
return /* @__PURE__ */ jsx("div", { className: "p-4 text-sm text-kumo-subtle", children: "Loading findings\u2026" });
|
|
68
|
+
}
|
|
69
|
+
if (state.error !== null) {
|
|
70
|
+
return /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
|
|
71
|
+
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "SEO findings" }),
|
|
72
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-2 rounded border border-red-500/40 bg-red-500/10 p-3 text-sm text-red-700", children: [
|
|
73
|
+
"Failed to load: ",
|
|
74
|
+
state.error
|
|
75
|
+
] }),
|
|
76
|
+
/* @__PURE__ */ jsx(
|
|
77
|
+
"button",
|
|
78
|
+
{
|
|
79
|
+
type: "button",
|
|
80
|
+
onClick: () => void load(),
|
|
81
|
+
className: "mt-3 rounded border border-kumo-line px-3 py-1.5 text-sm hover:bg-kumo-tint",
|
|
82
|
+
children: "Retry"
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
] });
|
|
86
|
+
}
|
|
87
|
+
const payload = state.payload;
|
|
88
|
+
if (payload === null) {
|
|
89
|
+
return /* @__PURE__ */ jsx("div", { className: "p-4 text-sm text-kumo-subtle", children: "No data." });
|
|
90
|
+
}
|
|
91
|
+
const routesByPath = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const route of payload.routes) {
|
|
93
|
+
routesByPath.set(route.route, route);
|
|
94
|
+
}
|
|
95
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-4", children: [
|
|
96
|
+
/* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between gap-4", children: [
|
|
97
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
98
|
+
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "SEO findings" }),
|
|
99
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-kumo-subtle", children: payload.totals.findings === 0 ? /* @__PURE__ */ jsx(Fragment, { children: "No findings yet \u2014 click Refresh to evaluate the site." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
100
|
+
payload.totals.findings,
|
|
101
|
+
" findings across ",
|
|
102
|
+
payload.totals.routes,
|
|
103
|
+
" routes \u2014",
|
|
104
|
+
" ",
|
|
105
|
+
payload.totals.errors,
|
|
106
|
+
" errors, ",
|
|
107
|
+
payload.totals.warnings,
|
|
108
|
+
" warnings."
|
|
109
|
+
] }) })
|
|
110
|
+
] }),
|
|
111
|
+
/* @__PURE__ */ jsx(
|
|
112
|
+
"button",
|
|
113
|
+
{
|
|
114
|
+
type: "button",
|
|
115
|
+
onClick: () => void refresh(),
|
|
116
|
+
disabled: refreshing,
|
|
117
|
+
className: "rounded bg-kumo-brand px-3 py-1.5 text-sm font-medium text-white hover:bg-kumo-brand/90 disabled:opacity-50",
|
|
118
|
+
children: refreshing ? "Refreshing\u2026" : "Refresh"
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
] }),
|
|
122
|
+
toast !== null && /* @__PURE__ */ jsx("div", { className: "rounded border border-kumo-line bg-kumo-tint p-3 text-sm", children: toast }),
|
|
123
|
+
payload.sitewideFindings.length > 0 && /* @__PURE__ */ jsxs("section", { className: "rounded border border-kumo-line p-3", children: [
|
|
124
|
+
/* @__PURE__ */ jsx("h2", { className: "text-sm font-medium", children: "Sitewide" }),
|
|
125
|
+
/* @__PURE__ */ jsx("ul", { className: "mt-2 space-y-1 text-sm", children: payload.sitewideFindings.map((finding, i) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
|
|
126
|
+
/* @__PURE__ */ jsx(SeverityBadge, { severity: finding.severity }),
|
|
127
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-xs", children: finding.rule_id }),
|
|
128
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1", children: finding.message })
|
|
129
|
+
] }, i)) })
|
|
130
|
+
] }),
|
|
131
|
+
payload.findings.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-kumo-subtle", children: payload.totals.documentsIndexed === 0 ? "No documents indexed \u2014 click Refresh to do an initial sweep." : "All indexed documents are clean." }) : /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded border border-kumo-line", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-left text-sm", children: [
|
|
132
|
+
/* @__PURE__ */ jsx("thead", { className: "bg-kumo-tint", children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
133
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Route" }),
|
|
134
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Rule" }),
|
|
135
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Severity" }),
|
|
136
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Message" })
|
|
137
|
+
] }) }),
|
|
138
|
+
/* @__PURE__ */ jsx("tbody", { children: payload.findings.map((finding, i) => {
|
|
139
|
+
const routeInfo = routesByPath.get(finding.route);
|
|
140
|
+
return /* @__PURE__ */ jsxs("tr", { className: "border-t border-kumo-line", children: [
|
|
141
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx(RouteLinks, { finding, info: routeInfo }) }),
|
|
142
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx("code", { className: "rounded bg-kumo-tint px-1.5 py-0.5 font-mono text-xs", children: finding.rule_id }) }),
|
|
143
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx(SeverityBadge, { severity: finding.severity }) }),
|
|
144
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: finding.message })
|
|
145
|
+
] }, i);
|
|
146
|
+
}) })
|
|
147
|
+
] }) })
|
|
148
|
+
] });
|
|
149
|
+
}
|
|
150
|
+
function RouteLinks({
|
|
151
|
+
finding,
|
|
152
|
+
info
|
|
153
|
+
}) {
|
|
154
|
+
if (info === void 0) {
|
|
155
|
+
return /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: finding.route });
|
|
156
|
+
}
|
|
157
|
+
return /* @__PURE__ */ jsxs("span", { className: "space-x-2", children: [
|
|
158
|
+
info.editUrl !== "" ? /* @__PURE__ */ jsx(
|
|
159
|
+
"a",
|
|
160
|
+
{
|
|
161
|
+
href: info.editUrl,
|
|
162
|
+
className: "font-mono text-xs text-kumo-link hover:underline",
|
|
163
|
+
children: finding.route
|
|
164
|
+
}
|
|
165
|
+
) : /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: finding.route }),
|
|
166
|
+
info.publishedUrl !== "" && /* @__PURE__ */ jsx(
|
|
167
|
+
"a",
|
|
168
|
+
{
|
|
169
|
+
href: info.publishedUrl,
|
|
170
|
+
target: "_blank",
|
|
171
|
+
rel: "noopener noreferrer",
|
|
172
|
+
className: "text-xs text-kumo-link hover:underline",
|
|
173
|
+
children: "live \u2197"
|
|
174
|
+
}
|
|
175
|
+
),
|
|
176
|
+
info.status !== "" && info.status !== "published" && /* @__PURE__ */ jsx("span", { className: "text-xs text-kumo-subtle", children: info.status })
|
|
177
|
+
] });
|
|
178
|
+
}
|
|
179
|
+
function SeverityBadge({ severity }) {
|
|
180
|
+
const classes = severity === "error" ? "bg-red-500/10 text-red-700 border-red-500/40" : severity === "warning" ? "bg-yellow-500/10 text-yellow-800 border-yellow-500/40" : "bg-kumo-tint text-kumo-subtle border-kumo-line";
|
|
181
|
+
return /* @__PURE__ */ jsx(
|
|
182
|
+
"span",
|
|
183
|
+
{
|
|
184
|
+
className: `inline-block rounded border px-1.5 py-0.5 text-xs font-medium ${classes}`,
|
|
185
|
+
children: severity
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
function csrfHeaders() {
|
|
190
|
+
return {
|
|
191
|
+
"Content-Type": "application/json",
|
|
192
|
+
// emdash's apiFetch wrapper sets this on every mutation;
|
|
193
|
+
// mirroring it here so the plugin's REST endpoints get past the
|
|
194
|
+
// server's CSRF check the same way emdash's own admin does.
|
|
195
|
+
"X-EmDash-Request": "1"
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function unwrap(body, res) {
|
|
199
|
+
if (body.error !== void 0) {
|
|
200
|
+
throw new Error(`${body.error.code}: ${body.error.message}`);
|
|
201
|
+
}
|
|
202
|
+
if (body.data === void 0) {
|
|
203
|
+
throw new Error(`Empty response (HTTP ${res.status})`);
|
|
204
|
+
}
|
|
205
|
+
return body.data;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/admin.tsx
|
|
209
|
+
var pages = {
|
|
210
|
+
"/findings": Findings
|
|
211
|
+
};
|
|
212
|
+
export {
|
|
213
|
+
pages
|
|
214
|
+
};
|
package/dist/configured.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AA8EA,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CA0E3E;AAID,eAAe,YAAY,CAAC"}
|
package/dist/configured.js
CHANGED
|
@@ -5,7 +5,7 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/adapter.ts
|
|
8
|
-
function
|
|
8
|
+
function adaptContentItem(content) {
|
|
9
9
|
const route = deriveRoute(content);
|
|
10
10
|
const title = stringOrEmpty(
|
|
11
11
|
content.seo?.title ?? content.data["title"] ?? content.slug ?? content.id
|
|
@@ -29,7 +29,16 @@ function contentItemToEmdashDocument(content) {
|
|
|
29
29
|
if (body !== null) {
|
|
30
30
|
document.body = body;
|
|
31
31
|
}
|
|
32
|
-
return
|
|
32
|
+
return {
|
|
33
|
+
document,
|
|
34
|
+
meta: {
|
|
35
|
+
id: content.id,
|
|
36
|
+
collection: content.type,
|
|
37
|
+
slug: content.slug,
|
|
38
|
+
status: content.status,
|
|
39
|
+
title
|
|
40
|
+
}
|
|
41
|
+
};
|
|
33
42
|
}
|
|
34
43
|
function deriveRoute(content) {
|
|
35
44
|
if (typeof content.slug === "string" && content.slug.length > 0) {
|
|
@@ -65,9 +74,11 @@ function isPortableTextArray(value) {
|
|
|
65
74
|
|
|
66
75
|
// src/plugin.ts
|
|
67
76
|
async function handleAfterSaveConfigured(event, ctx, evaluator) {
|
|
68
|
-
const
|
|
77
|
+
const adapted = adaptContentItem(event.content);
|
|
78
|
+
const document = adapted.document;
|
|
69
79
|
const { kv, log } = ctx;
|
|
70
|
-
|
|
80
|
+
const stored = { document, meta: adapted.meta };
|
|
81
|
+
await kv.set(documentKey(document.route), stored);
|
|
71
82
|
const result = await evaluator([document]);
|
|
72
83
|
if (!result.ok) {
|
|
73
84
|
log?.error?.(`afterSave evaluator failure (${result.reason})`, {
|
|
@@ -94,6 +105,7 @@ async function evaluateAndPersistAll(ctx, options) {
|
|
|
94
105
|
errors: []
|
|
95
106
|
};
|
|
96
107
|
const documents = [];
|
|
108
|
+
const adaptedByRoute = /* @__PURE__ */ new Map();
|
|
97
109
|
const documentRoutes = /* @__PURE__ */ new Set();
|
|
98
110
|
for (const collection of collections) {
|
|
99
111
|
let cursor;
|
|
@@ -104,9 +116,13 @@ async function evaluateAndPersistAll(ctx, options) {
|
|
|
104
116
|
...cursor === void 0 ? {} : { cursor }
|
|
105
117
|
});
|
|
106
118
|
for (const item of page.items) {
|
|
107
|
-
const
|
|
108
|
-
documents.push(document);
|
|
109
|
-
documentRoutes.add(document.route);
|
|
119
|
+
const adapted = adaptContentItem(item);
|
|
120
|
+
documents.push(adapted.document);
|
|
121
|
+
documentRoutes.add(adapted.document.route);
|
|
122
|
+
adaptedByRoute.set(adapted.document.route, {
|
|
123
|
+
document: adapted.document,
|
|
124
|
+
meta: adapted.meta
|
|
125
|
+
});
|
|
110
126
|
}
|
|
111
127
|
cursor = page.hasMore ? page.cursor : void 0;
|
|
112
128
|
} catch (err) {
|
|
@@ -117,8 +133,8 @@ async function evaluateAndPersistAll(ctx, options) {
|
|
|
117
133
|
} while (cursor !== void 0);
|
|
118
134
|
}
|
|
119
135
|
summary.documentsScanned = documents.length;
|
|
120
|
-
for (const
|
|
121
|
-
await kv.set(documentKey(document.route),
|
|
136
|
+
for (const stored of adaptedByRoute.values()) {
|
|
137
|
+
await kv.set(documentKey(stored.document.route), stored);
|
|
122
138
|
}
|
|
123
139
|
const result = await options.evaluator(documents);
|
|
124
140
|
if (!result.ok) {
|
|
@@ -154,8 +170,33 @@ function documentKey(route) {
|
|
|
154
170
|
return `document:${normalized}`;
|
|
155
171
|
}
|
|
156
172
|
async function readAllDocuments(kv) {
|
|
173
|
+
return (await readAllStoredDocuments(kv)).map((s) => s.document);
|
|
174
|
+
}
|
|
175
|
+
async function readAllStoredDocuments(kv) {
|
|
157
176
|
const entries = await kv.list("document:");
|
|
158
|
-
|
|
177
|
+
const out = [];
|
|
178
|
+
for (const entry of entries) {
|
|
179
|
+
const value = entry.value;
|
|
180
|
+
if (value === null || typeof value !== "object") continue;
|
|
181
|
+
if ("document" in value && "meta" in value) {
|
|
182
|
+
out.push(value);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if ("route" in value) {
|
|
186
|
+
const document = value;
|
|
187
|
+
out.push({
|
|
188
|
+
document,
|
|
189
|
+
meta: {
|
|
190
|
+
id: document.route,
|
|
191
|
+
collection: "",
|
|
192
|
+
slug: null,
|
|
193
|
+
status: "",
|
|
194
|
+
title: document.title ?? ""
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return out;
|
|
159
200
|
}
|
|
160
201
|
async function readFindings(kv, route) {
|
|
161
202
|
const stored = await kv.get(findingsKey(route));
|
|
@@ -165,6 +206,97 @@ async function readFindings(kv, route) {
|
|
|
165
206
|
return stored.findings;
|
|
166
207
|
}
|
|
167
208
|
|
|
209
|
+
// src/data-route.ts
|
|
210
|
+
async function handleDataRoute(ctx, options) {
|
|
211
|
+
let summary = null;
|
|
212
|
+
if (options.refresh) {
|
|
213
|
+
summary = await evaluateAndPersistAll(ctx, {
|
|
214
|
+
collections: options.collections,
|
|
215
|
+
evaluator: options.evaluator
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
const payload = await buildFindingsPayload(ctx);
|
|
219
|
+
return { payload, summary };
|
|
220
|
+
}
|
|
221
|
+
async function buildFindingsPayload(ctx) {
|
|
222
|
+
const stored = await readAllStoredDocuments(ctx.kv);
|
|
223
|
+
const documents = /* @__PURE__ */ new Map();
|
|
224
|
+
for (const entry of stored) {
|
|
225
|
+
documents.set(entry.document.route, entry);
|
|
226
|
+
}
|
|
227
|
+
const entries = await ctx.kv.list(
|
|
228
|
+
"findings:"
|
|
229
|
+
);
|
|
230
|
+
const findings = [];
|
|
231
|
+
const sitewide = [];
|
|
232
|
+
const perRoute = /* @__PURE__ */ new Map();
|
|
233
|
+
for (const entry of entries) {
|
|
234
|
+
if (entry.value === null) continue;
|
|
235
|
+
const route = entry.key.replace(/^findings:/, "");
|
|
236
|
+
if (route === "*") {
|
|
237
|
+
sitewide.push(...entry.value.findings);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
let bucket = perRoute.get(route);
|
|
241
|
+
if (bucket === void 0) {
|
|
242
|
+
bucket = { findings: [], errors: 0, warnings: 0 };
|
|
243
|
+
perRoute.set(route, bucket);
|
|
244
|
+
}
|
|
245
|
+
for (const finding of entry.value.findings) {
|
|
246
|
+
bucket.findings.push(finding);
|
|
247
|
+
if (finding.severity === "error") bucket.errors += 1;
|
|
248
|
+
else if (finding.severity === "warning") bucket.warnings += 1;
|
|
249
|
+
findings.push({ ...finding, route });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
findings.sort((a, b) => severityRank(a.severity) - severityRank(b.severity));
|
|
253
|
+
const siteUrl = ctx.site?.url ?? "";
|
|
254
|
+
const routes = [];
|
|
255
|
+
for (const [route, bucket] of perRoute) {
|
|
256
|
+
const stored2 = documents.get(route);
|
|
257
|
+
routes.push({
|
|
258
|
+
route,
|
|
259
|
+
title: stored2?.meta.title ?? route,
|
|
260
|
+
status: stored2?.meta.status ?? "",
|
|
261
|
+
collection: stored2?.meta.collection ?? "",
|
|
262
|
+
id: stored2?.meta.id ?? "",
|
|
263
|
+
editUrl: stored2 ? buildEditUrl(stored2.meta) : "",
|
|
264
|
+
publishedUrl: stored2 ? buildPublishedUrl(stored2.meta, route, siteUrl) : "",
|
|
265
|
+
findingCount: bucket.findings.length,
|
|
266
|
+
errorCount: bucket.errors,
|
|
267
|
+
warningCount: bucket.warnings
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
routes.sort((a, b) => b.errorCount - a.errorCount || a.route.localeCompare(b.route));
|
|
271
|
+
return {
|
|
272
|
+
routes,
|
|
273
|
+
findings,
|
|
274
|
+
totals: {
|
|
275
|
+
routes: routes.length,
|
|
276
|
+
findings: findings.length,
|
|
277
|
+
errors: findings.filter((f) => f.severity === "error").length,
|
|
278
|
+
warnings: findings.filter((f) => f.severity === "warning").length,
|
|
279
|
+
documentsIndexed: documents.size
|
|
280
|
+
},
|
|
281
|
+
sitewideFindings: sitewide
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function severityRank(severity) {
|
|
285
|
+
if (severity === "error") return 0;
|
|
286
|
+
if (severity === "warning") return 1;
|
|
287
|
+
return 2;
|
|
288
|
+
}
|
|
289
|
+
function buildEditUrl(meta) {
|
|
290
|
+
if (meta.collection === "" || meta.id === "") return "";
|
|
291
|
+
return `/_emdash/admin/content/${encodeURIComponent(meta.collection)}/${encodeURIComponent(meta.id)}`;
|
|
292
|
+
}
|
|
293
|
+
function buildPublishedUrl(meta, route, siteUrl) {
|
|
294
|
+
if (meta.status !== "published") return "";
|
|
295
|
+
if (siteUrl === "") return "";
|
|
296
|
+
const trimmed = siteUrl.endsWith("/") ? siteUrl.slice(0, -1) : siteUrl;
|
|
297
|
+
return `${trimmed}${route}`;
|
|
298
|
+
}
|
|
299
|
+
|
|
168
300
|
// wasm/aexeo_emdash_bridge_bg.js
|
|
169
301
|
var aexeo_emdash_bridge_bg_exports = {};
|
|
170
302
|
__export(aexeo_emdash_bridge_bg_exports, {
|
|
@@ -375,6 +507,27 @@ function createPlugin(options = {}) {
|
|
|
375
507
|
// every request handles the same set without re-reading options.
|
|
376
508
|
admin: {
|
|
377
509
|
handler: (ctx) => handleAdminRoute(ctx, collections)
|
|
510
|
+
},
|
|
511
|
+
// JSON data endpoint for the React adminEntry. Two routes:
|
|
512
|
+
// POST /_emdash/api/plugins/aexeo-seogeo/data — read current findings
|
|
513
|
+
// POST /_emdash/api/plugins/aexeo-seogeo/refresh — sweep + read
|
|
514
|
+
// Returning JSON (not Block Kit blocks) lets the React
|
|
515
|
+
// <Findings/> component render proper <a href> links to the
|
|
516
|
+
// emdash edit page and the public site, which Block Kit can't
|
|
517
|
+
// express in any element type as of emdash 0.8.0.
|
|
518
|
+
data: {
|
|
519
|
+
handler: async (ctx) => (await handleDataRoute(ctx, {
|
|
520
|
+
collections,
|
|
521
|
+
evaluator: inProcessEvaluator,
|
|
522
|
+
refresh: false
|
|
523
|
+
})).payload
|
|
524
|
+
},
|
|
525
|
+
refresh: {
|
|
526
|
+
handler: (ctx) => handleDataRoute(ctx, {
|
|
527
|
+
collections,
|
|
528
|
+
evaluator: inProcessEvaluator,
|
|
529
|
+
refresh: true
|
|
530
|
+
})
|
|
378
531
|
}
|
|
379
532
|
},
|
|
380
533
|
admin: {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { EvaluatorFn, RefreshSummary, SandboxCtx } from "./plugin.js";
|
|
2
|
+
import type { Finding } from "./types.js";
|
|
3
|
+
export interface FindingsPayload {
|
|
4
|
+
routes: RouteSummary[];
|
|
5
|
+
findings: FindingRow[];
|
|
6
|
+
totals: {
|
|
7
|
+
routes: number;
|
|
8
|
+
findings: number;
|
|
9
|
+
errors: number;
|
|
10
|
+
warnings: number;
|
|
11
|
+
documentsIndexed: number;
|
|
12
|
+
};
|
|
13
|
+
sitewideFindings: Finding[];
|
|
14
|
+
}
|
|
15
|
+
export interface RouteSummary {
|
|
16
|
+
route: string;
|
|
17
|
+
title: string;
|
|
18
|
+
status: string;
|
|
19
|
+
collection: string;
|
|
20
|
+
id: string;
|
|
21
|
+
editUrl: string;
|
|
22
|
+
publishedUrl: string;
|
|
23
|
+
findingCount: number;
|
|
24
|
+
errorCount: number;
|
|
25
|
+
warningCount: number;
|
|
26
|
+
}
|
|
27
|
+
export interface FindingRow {
|
|
28
|
+
route: string;
|
|
29
|
+
rule_id: string;
|
|
30
|
+
severity: string;
|
|
31
|
+
message: string;
|
|
32
|
+
path: string;
|
|
33
|
+
line: number;
|
|
34
|
+
column: number;
|
|
35
|
+
scope: string;
|
|
36
|
+
suggestion: string | null;
|
|
37
|
+
}
|
|
38
|
+
interface DataRouteOptions {
|
|
39
|
+
collections: readonly string[];
|
|
40
|
+
evaluator: EvaluatorFn;
|
|
41
|
+
refresh: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare function handleDataRoute(ctx: SandboxCtx, options: DataRouteOptions): Promise<{
|
|
44
|
+
payload: FindingsPayload;
|
|
45
|
+
summary: RefreshSummary | null;
|
|
46
|
+
}>;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=data-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-route.d.ts","sourceRoot":"","sources":["../src/data-route.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,UAAU,EAEX,MAAM,aAAa,CAAC;AAMrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAK1C,MAAM,WAAW,eAAe;IAG9B,MAAM,EAAE,YAAY,EAAE,CAAC;IAIvB,QAAQ,EAAE,UAAU,EAAE,CAAC;IAIvB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IAIF,gBAAgB,EAAE,OAAO,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IAGd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IAIX,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,UAAU,gBAAgB;IACxB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,SAAS,EAAE,WAAW,CAAC;IAIvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC;IACT,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;CAChC,CAAC,CAUD"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAWtB,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAWtB,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IAOnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,UAAU,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,YAAY,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACxE;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,0BAA0B,CA+C5B"}
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,15 @@ function seogeoPlugin(options = {}) {
|
|
|
57
57
|
// time — must match the package.json `exports["./configured"]`
|
|
58
58
|
// entry.
|
|
59
59
|
entrypoint: "@aeptus/aexeo-emdash/configured",
|
|
60
|
+
// React admin module — registers a custom <Findings/> component
|
|
61
|
+
// for /findings so the route column can render proper <a href>
|
|
62
|
+
// links. Block Kit can't express clickable links in any element
|
|
63
|
+
// type as of emdash 0.8.0. The React module's `pages` object is
|
|
64
|
+
// imported by emdash's admin registry codegen at build time; if
|
|
65
|
+
// the consumer's bundler doesn't pick up tsx (rare — every
|
|
66
|
+
// emdash adapter does), the rest of the plugin still works
|
|
67
|
+
// because /document and the dashboard widget stay on Block Kit.
|
|
68
|
+
adminEntry: "@aeptus/aexeo-emdash/admin",
|
|
60
69
|
// emdash's astro integration JSON-serializes `options` and emits
|
|
61
70
|
// an importer that does `createPlugin(<options>)` at boot.
|
|
62
71
|
// Anything we put here is what the runtime entry's createPlugin
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { EmdashDocument, Finding } from "./types.js";
|
|
2
|
-
import { type EmdashContentItem } from "./adapter.js";
|
|
2
|
+
import { type EmdashContentItem, type EmdashContentMeta } from "./adapter.js";
|
|
3
3
|
import type { SidecarHttp } from "./sidecar.js";
|
|
4
|
+
export interface StoredDocument {
|
|
5
|
+
document: EmdashDocument;
|
|
6
|
+
meta: EmdashContentMeta;
|
|
7
|
+
}
|
|
4
8
|
export declare function buildCapabilities(evaluatorHost: string | null): readonly string[];
|
|
5
9
|
export declare function buildAllowedHosts(evaluatorHost: string | null): readonly string[];
|
|
6
10
|
export declare const capabilities: readonly string[];
|
|
@@ -37,6 +41,11 @@ export interface SandboxCtx {
|
|
|
37
41
|
kv: KvNamespace;
|
|
38
42
|
http: SidecarHttp;
|
|
39
43
|
content: SandboxContentApi;
|
|
44
|
+
site?: {
|
|
45
|
+
url: string;
|
|
46
|
+
name?: string;
|
|
47
|
+
locale?: string;
|
|
48
|
+
};
|
|
40
49
|
log?: {
|
|
41
50
|
info(msg: string, data?: unknown): void;
|
|
42
51
|
warn(msg: string, data?: unknown): void;
|
|
@@ -73,5 +82,6 @@ export declare function evaluateAndPersistAll(ctx: SandboxCtx, options: {
|
|
|
73
82
|
export declare function findingsKey(route: string): string;
|
|
74
83
|
export declare function documentKey(route: string): string;
|
|
75
84
|
export declare function readAllDocuments(kv: KvNamespace): Promise<EmdashDocument[]>;
|
|
85
|
+
export declare function readAllStoredDocuments(kv: KvNamespace): Promise<StoredDocument[]>;
|
|
76
86
|
export declare function readFindings(kv: KvNamespace, route: string): Promise<Finding[]>;
|
|
77
87
|
//# sourceMappingURL=plugin.d.ts.map
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EACL,KAAK,iBAAiB,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/dist/sandbox-entry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/adapter.ts
|
|
2
|
-
function
|
|
2
|
+
function adaptContentItem(content) {
|
|
3
3
|
const route = deriveRoute(content);
|
|
4
4
|
const title = stringOrEmpty(
|
|
5
5
|
content.seo?.title ?? content.data["title"] ?? content.slug ?? content.id
|
|
@@ -23,7 +23,16 @@ function contentItemToEmdashDocument(content) {
|
|
|
23
23
|
if (body !== null) {
|
|
24
24
|
document.body = body;
|
|
25
25
|
}
|
|
26
|
-
return
|
|
26
|
+
return {
|
|
27
|
+
document,
|
|
28
|
+
meta: {
|
|
29
|
+
id: content.id,
|
|
30
|
+
collection: content.type,
|
|
31
|
+
slug: content.slug,
|
|
32
|
+
status: content.status,
|
|
33
|
+
title
|
|
34
|
+
}
|
|
35
|
+
};
|
|
27
36
|
}
|
|
28
37
|
function deriveRoute(content) {
|
|
29
38
|
if (typeof content.slug === "string" && content.slug.length > 0) {
|
|
@@ -88,6 +97,7 @@ async function evaluateAndPersistAll(ctx, options) {
|
|
|
88
97
|
errors: []
|
|
89
98
|
};
|
|
90
99
|
const documents = [];
|
|
100
|
+
const adaptedByRoute = /* @__PURE__ */ new Map();
|
|
91
101
|
const documentRoutes = /* @__PURE__ */ new Set();
|
|
92
102
|
for (const collection of collections) {
|
|
93
103
|
let cursor;
|
|
@@ -98,9 +108,13 @@ async function evaluateAndPersistAll(ctx, options) {
|
|
|
98
108
|
...cursor === void 0 ? {} : { cursor }
|
|
99
109
|
});
|
|
100
110
|
for (const item of page.items) {
|
|
101
|
-
const
|
|
102
|
-
documents.push(document);
|
|
103
|
-
documentRoutes.add(document.route);
|
|
111
|
+
const adapted = adaptContentItem(item);
|
|
112
|
+
documents.push(adapted.document);
|
|
113
|
+
documentRoutes.add(adapted.document.route);
|
|
114
|
+
adaptedByRoute.set(adapted.document.route, {
|
|
115
|
+
document: adapted.document,
|
|
116
|
+
meta: adapted.meta
|
|
117
|
+
});
|
|
104
118
|
}
|
|
105
119
|
cursor = page.hasMore ? page.cursor : void 0;
|
|
106
120
|
} catch (err) {
|
|
@@ -111,8 +125,8 @@ async function evaluateAndPersistAll(ctx, options) {
|
|
|
111
125
|
} while (cursor !== void 0);
|
|
112
126
|
}
|
|
113
127
|
summary.documentsScanned = documents.length;
|
|
114
|
-
for (const
|
|
115
|
-
await kv.set(documentKey(document.route),
|
|
128
|
+
for (const stored of adaptedByRoute.values()) {
|
|
129
|
+
await kv.set(documentKey(stored.document.route), stored);
|
|
116
130
|
}
|
|
117
131
|
const result = await options.evaluator(documents);
|
|
118
132
|
if (!result.ok) {
|
|
@@ -148,8 +162,33 @@ function documentKey(route) {
|
|
|
148
162
|
return `document:${normalized}`;
|
|
149
163
|
}
|
|
150
164
|
async function readAllDocuments(kv) {
|
|
165
|
+
return (await readAllStoredDocuments(kv)).map((s) => s.document);
|
|
166
|
+
}
|
|
167
|
+
async function readAllStoredDocuments(kv) {
|
|
151
168
|
const entries = await kv.list("document:");
|
|
152
|
-
|
|
169
|
+
const out = [];
|
|
170
|
+
for (const entry of entries) {
|
|
171
|
+
const value = entry.value;
|
|
172
|
+
if (value === null || typeof value !== "object") continue;
|
|
173
|
+
if ("document" in value && "meta" in value) {
|
|
174
|
+
out.push(value);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if ("route" in value) {
|
|
178
|
+
const document = value;
|
|
179
|
+
out.push({
|
|
180
|
+
document,
|
|
181
|
+
meta: {
|
|
182
|
+
id: document.route,
|
|
183
|
+
collection: "",
|
|
184
|
+
slug: null,
|
|
185
|
+
status: "",
|
|
186
|
+
title: document.title ?? ""
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
153
192
|
}
|
|
154
193
|
async function readFindings(kv, route) {
|
|
155
194
|
const stored = await kv.get(findingsKey(route));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aeptus/aexeo-emdash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Aexeo seogeo SEO/GEO content evaluator as an emdash plugin",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Aexeo",
|
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
"import": "./dist/configured.js",
|
|
37
37
|
"default": "./dist/configured.js"
|
|
38
38
|
},
|
|
39
|
+
"./admin": {
|
|
40
|
+
"types": "./dist/admin.d.ts",
|
|
41
|
+
"import": "./dist/admin.js",
|
|
42
|
+
"default": "./dist/admin.js"
|
|
43
|
+
},
|
|
39
44
|
"./sandbox": {
|
|
40
45
|
"types": "./dist/sandbox-entry.d.ts",
|
|
41
46
|
"import": "./dist/sandbox-entry.js",
|
|
@@ -59,7 +64,8 @@
|
|
|
59
64
|
},
|
|
60
65
|
"peerDependencies": {
|
|
61
66
|
"@emdash-cms/core": "*",
|
|
62
|
-
"emdash": ">=0.7.0"
|
|
67
|
+
"emdash": ">=0.7.0",
|
|
68
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
63
69
|
},
|
|
64
70
|
"peerDependenciesMeta": {
|
|
65
71
|
"@emdash-cms/core": {
|
|
@@ -67,12 +73,17 @@
|
|
|
67
73
|
},
|
|
68
74
|
"emdash": {
|
|
69
75
|
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"react": {
|
|
78
|
+
"optional": true
|
|
70
79
|
}
|
|
71
80
|
},
|
|
72
81
|
"devDependencies": {
|
|
73
82
|
"@types/node": "^22.0.0",
|
|
83
|
+
"@types/react": "^19.2.14",
|
|
74
84
|
"emdash": "^0.7.0",
|
|
75
85
|
"esbuild": "^0.28.0",
|
|
86
|
+
"react": "^19.2.5",
|
|
76
87
|
"typescript": "^5.6.0"
|
|
77
88
|
}
|
|
78
89
|
}
|