@aeptus/aexeo-emdash 0.1.1 → 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 +88 -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 +8 -1
- package/dist/configured.d.ts.map +1 -1
- package/dist/configured.js +184 -21
- package/dist/data-route.d.ts +48 -0
- package/dist/data-route.d.ts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -5
- 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,91 @@ 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
|
+
|
|
62
|
+
## [0.1.2] - 2026-04-28
|
|
63
|
+
|
|
64
|
+
Functional patch. Three bugs flagged by the aeptus web team after
|
|
65
|
+
testing 0.1.1 against emdash 0.8.0; same `wasm/` as `0.1.0` (no
|
|
66
|
+
rule-engine change).
|
|
67
|
+
|
|
68
|
+
**Compatibility:** verified against emdash `0.7.0` and `0.8.0`.
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
|
|
72
|
+
- **Duplicate "SEO findings" sidebar entry.** The descriptor and
|
|
73
|
+
the runtime admin both declared `/` AND `/findings` as adminPages
|
|
74
|
+
with the same label, so emdash rendered two duplicate links.
|
|
75
|
+
Drop the `/` entry — root-URL navigation
|
|
76
|
+
(`/admin/plugins/<id>/`) still works because the dispatcher in
|
|
77
|
+
`src/configured.ts` aliases the empty page name to the findings
|
|
78
|
+
page.
|
|
79
|
+
|
|
80
|
+
### Added
|
|
81
|
+
|
|
82
|
+
- **`seogeoPlugin({ collections })` option** for configured mode.
|
|
83
|
+
Lets sites with non-default collection slugs override which
|
|
84
|
+
collections the Refresh button sweeps. Defaults to
|
|
85
|
+
`["posts", "pages"]` when omitted (the slugs from
|
|
86
|
+
`@emdash-cms/template-blog-cloudflare`). Pointing at a missing
|
|
87
|
+
slug is non-fatal — the bridge returns empty and the sweep
|
|
88
|
+
records the missing collection in the Refresh summary's `errors`.
|
|
89
|
+
- The Refresh sweep now threads the resolved collection list
|
|
90
|
+
through `handleAdminRoute` → `handleRefresh` →
|
|
91
|
+
`evaluateAndPersistAll` so the runtime honors what the descriptor
|
|
92
|
+
passed.
|
|
93
|
+
|
|
9
94
|
## [0.1.1] - 2026-04-28
|
|
10
95
|
|
|
11
96
|
Documentation-only patch. No runtime changes — same `dist/` and
|
|
@@ -68,6 +153,8 @@ First public release.
|
|
|
68
153
|
`astro.config.mjs` for the WASM import to resolve to a
|
|
69
154
|
precompiled `WebAssembly.Module`. Not optional.
|
|
70
155
|
|
|
71
|
-
[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
|
|
158
|
+
[0.1.2]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.1...aexeo-emdash-v0.1.2
|
|
72
159
|
[0.1.1]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.0...aexeo-emdash-v0.1.1
|
|
73
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
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export interface ConfiguredPluginOptions {
|
|
2
|
+
/**
|
|
3
|
+
* emdash collections the Refresh button sweeps. Comes through from
|
|
4
|
+
* the descriptor's `options` field — see `seogeoPlugin({ collections })`
|
|
5
|
+
* in src/index.ts. When undefined, falls back to DEFAULT_COLLECTIONS
|
|
6
|
+
* (`["posts", "pages"]`).
|
|
7
|
+
*/
|
|
8
|
+
collections?: readonly string[];
|
|
2
9
|
}
|
|
3
|
-
export declare function createPlugin(
|
|
10
|
+
export declare function createPlugin(options?: ConfiguredPluginOptions): unknown;
|
|
4
11
|
export default createPlugin;
|
|
5
12
|
//# sourceMappingURL=configured.d.ts.map
|
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, {
|
|
@@ -357,24 +489,54 @@ var inProcessEvaluator = async (documents) => {
|
|
|
357
489
|
return { ok: false, reason: "wasm_error", detail };
|
|
358
490
|
}
|
|
359
491
|
};
|
|
360
|
-
function createPlugin(
|
|
492
|
+
function createPlugin(options = {}) {
|
|
493
|
+
const collections = options.collections === void 0 ? DEFAULT_COLLECTIONS : [...options.collections];
|
|
361
494
|
return definePlugin({
|
|
362
495
|
id: "aexeo-seogeo",
|
|
363
496
|
version: "0.0.1",
|
|
364
497
|
capabilities: ["read:content"],
|
|
365
498
|
hooks: {
|
|
499
|
+
// afterSave processes one saved document at a time — collections
|
|
500
|
+
// list isn't needed here. The hook always runs regardless of
|
|
501
|
+
// which collection the document came from.
|
|
366
502
|
"content:afterSave": (event, ctx) => handleAfterSaveConfigured(event, ctx, inProcessEvaluator)
|
|
367
503
|
},
|
|
368
504
|
routes: {
|
|
369
|
-
|
|
505
|
+
// The Refresh sweep (handleAdminRoute → handleRefresh) is the
|
|
506
|
+
// one consumer of `collections`. Bind the resolved list here so
|
|
507
|
+
// every request handles the same set without re-reading options.
|
|
508
|
+
admin: {
|
|
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
|
+
})
|
|
531
|
+
}
|
|
370
532
|
},
|
|
371
533
|
admin: {
|
|
372
534
|
pages: [
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
|
|
535
|
+
// The root URL `/admin/plugins/<id>/` routes through the same
|
|
536
|
+
// dispatcher (handlePageLoad treats "" and "findings" as
|
|
537
|
+
// aliases). Listing "/" alongside "/findings" here would create
|
|
538
|
+
// duplicate "SEO findings" sidebar entries — that was the
|
|
539
|
+
// visible 0.1.0 / 0.1.1 bug fixed in 0.1.2.
|
|
378
540
|
{ path: "/findings", label: "SEO findings" },
|
|
379
541
|
{ path: "/document", label: "Document SEO" }
|
|
380
542
|
],
|
|
@@ -385,7 +547,7 @@ function createPlugin(_options = {}) {
|
|
|
385
547
|
});
|
|
386
548
|
}
|
|
387
549
|
var configured_default = createPlugin;
|
|
388
|
-
async function handleAdminRoute(ctx) {
|
|
550
|
+
async function handleAdminRoute(ctx, collections) {
|
|
389
551
|
const body = ctx.input;
|
|
390
552
|
if (body.type === "page_load") {
|
|
391
553
|
return handlePageLoad(ctx, body.page);
|
|
@@ -395,7 +557,7 @@ async function handleAdminRoute(ctx) {
|
|
|
395
557
|
return renderDocumentPanel(ctx, body.value);
|
|
396
558
|
}
|
|
397
559
|
if (body.action_id === "refresh_findings") {
|
|
398
|
-
return handleRefresh(ctx);
|
|
560
|
+
return handleRefresh(ctx, collections);
|
|
399
561
|
}
|
|
400
562
|
if (body.action_id.startsWith("filter:")) {
|
|
401
563
|
return renderFindingsPage(ctx);
|
|
@@ -426,11 +588,12 @@ async function handlePageLoad(ctx, page) {
|
|
|
426
588
|
}
|
|
427
589
|
return notFound(page);
|
|
428
590
|
}
|
|
429
|
-
async function handleRefresh(ctx) {
|
|
591
|
+
async function handleRefresh(ctx, collections) {
|
|
430
592
|
let summary;
|
|
431
593
|
try {
|
|
432
594
|
summary = await evaluateAndPersistAll(ctx, {
|
|
433
|
-
evaluator: inProcessEvaluator
|
|
595
|
+
evaluator: inProcessEvaluator,
|
|
596
|
+
collections
|
|
434
597
|
});
|
|
435
598
|
} catch (err) {
|
|
436
599
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -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
|
@@ -4,6 +4,7 @@ export interface ConfiguredPluginDescriptor {
|
|
|
4
4
|
id: string;
|
|
5
5
|
version: string;
|
|
6
6
|
entrypoint: string;
|
|
7
|
+
adminEntry?: string;
|
|
7
8
|
options: Record<string, unknown>;
|
|
8
9
|
capabilities: readonly string[];
|
|
9
10
|
allowedHosts?: readonly string[];
|
|
@@ -18,6 +19,27 @@ export interface ConfiguredPluginDescriptor {
|
|
|
18
19
|
}[];
|
|
19
20
|
}
|
|
20
21
|
export interface SeogeoPluginOptions {
|
|
22
|
+
/**
|
|
23
|
+
* emdash collections the plugin sweeps when an admin clicks
|
|
24
|
+
* Refresh on the findings page. Order doesn't matter; the sweep
|
|
25
|
+
* iterates each collection sequentially and groups findings by
|
|
26
|
+
* document route. The per-document `content:afterSave` hook always
|
|
27
|
+
* runs regardless of this list — saving a document in any
|
|
28
|
+
* collection triggers an evaluation for that one document.
|
|
29
|
+
*
|
|
30
|
+
* Defaults to `["posts", "pages"]` (the slugs the
|
|
31
|
+
* `@emdash-cms/template-blog-cloudflare` template ships with).
|
|
32
|
+
* Override when your schema uses different collection slugs:
|
|
33
|
+
*
|
|
34
|
+
* seogeoPlugin({ collections: ["posts", "guides", "products"] })
|
|
35
|
+
*
|
|
36
|
+
* Pointing this at a slug that doesn't exist in your schema is
|
|
37
|
+
* non-fatal: the bridge's `content.list` returns empty and the
|
|
38
|
+
* sweep records the missing collection in the Refresh summary's
|
|
39
|
+
* `errors` field. Safe to ship a superset for a project that may
|
|
40
|
+
* add collections later.
|
|
41
|
+
*/
|
|
42
|
+
collections?: readonly string[];
|
|
21
43
|
}
|
|
22
44
|
export declare function seogeoPlugin(options?: SeogeoPluginOptions): ConfiguredPluginDescriptor;
|
|
23
45
|
//# sourceMappingURL=index.d.ts.map
|
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,17 +57,36 @@ function seogeoPlugin(options = {}) {
|
|
|
57
57
|
// time — must match the package.json `exports["./configured"]`
|
|
58
58
|
// entry.
|
|
59
59
|
entrypoint: "@aeptus/aexeo-emdash/configured",
|
|
60
|
-
|
|
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",
|
|
69
|
+
// emdash's astro integration JSON-serializes `options` and emits
|
|
70
|
+
// an importer that does `createPlugin(<options>)` at boot.
|
|
71
|
+
// Anything we put here is what the runtime entry's createPlugin
|
|
72
|
+
// receives. Spread to a plain object so readonly arrays survive
|
|
73
|
+
// the structured-clone semantics of that codegen.
|
|
74
|
+
options: {
|
|
75
|
+
...options.collections === void 0 ? {} : { collections: [...options.collections] }
|
|
76
|
+
},
|
|
61
77
|
capabilities: [
|
|
62
78
|
"read:content",
|
|
63
79
|
"read:schema",
|
|
64
80
|
"kv:seogeo-baselines"
|
|
65
81
|
],
|
|
82
|
+
// adminPages drives the admin sidebar — one nav entry per item.
|
|
83
|
+
// Don't list "/" alongside "/findings": both would render as
|
|
84
|
+
// duplicate "SEO findings" links in the sidebar (0.1.0 / 0.1.1
|
|
85
|
+
// shipped with this duplicate; fixed in 0.1.2). The root URL
|
|
86
|
+
// /admin/plugins/<id>/ still routes correctly because the
|
|
87
|
+
// dispatcher in src/configured.ts treats "" and "findings" as
|
|
88
|
+
// the same page.
|
|
66
89
|
adminPages: [
|
|
67
|
-
// "/" alias is required so emdash's /admin/plugins/<id> root
|
|
68
|
-
// doesn't 404 — that page is where the meta /admin/plugins
|
|
69
|
-
// list links to when an operator clicks the plugin entry.
|
|
70
|
-
{ path: "/", label: "SEO findings" },
|
|
71
90
|
{ path: "/findings", label: "SEO findings" },
|
|
72
91
|
{ path: "/document", label: "Document SEO" }
|
|
73
92
|
],
|
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
|
}
|