@aeptus/aexeo-emdash 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/CHANGELOG.md CHANGED
@@ -6,6 +6,58 @@ and the project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.2] - 2026-04-28
10
+
11
+ Functional patch. Three bugs flagged by the aeptus web team after
12
+ testing 0.1.1 against emdash 0.8.0; same `wasm/` as `0.1.0` (no
13
+ rule-engine change).
14
+
15
+ **Compatibility:** verified against emdash `0.7.0` and `0.8.0`.
16
+
17
+ ### Fixed
18
+
19
+ - **Duplicate "SEO findings" sidebar entry.** The descriptor and
20
+ the runtime admin both declared `/` AND `/findings` as adminPages
21
+ with the same label, so emdash rendered two duplicate links.
22
+ Drop the `/` entry — root-URL navigation
23
+ (`/admin/plugins/<id>/`) still works because the dispatcher in
24
+ `src/configured.ts` aliases the empty page name to the findings
25
+ page.
26
+
27
+ ### Added
28
+
29
+ - **`seogeoPlugin({ collections })` option** for configured mode.
30
+ Lets sites with non-default collection slugs override which
31
+ collections the Refresh button sweeps. Defaults to
32
+ `["posts", "pages"]` when omitted (the slugs from
33
+ `@emdash-cms/template-blog-cloudflare`). Pointing at a missing
34
+ slug is non-fatal — the bridge returns empty and the sweep
35
+ records the missing collection in the Refresh summary's `errors`.
36
+ - The Refresh sweep now threads the resolved collection list
37
+ through `handleAdminRoute` → `handleRefresh` →
38
+ `evaluateAndPersistAll` so the runtime honors what the descriptor
39
+ passed.
40
+
41
+ ## [0.1.1] - 2026-04-28
42
+
43
+ Documentation-only patch. No runtime changes — same `dist/` and
44
+ `wasm/` as `0.1.0`. Bumping the patch so npm consumers can resolve
45
+ to the documented version of INSTALL.md / CHANGELOG.md without
46
+ having to read this repo directly.
47
+
48
+ ### Changed
49
+
50
+ - INSTALL.md now has a structured "Updating" section covering
51
+ routine plugin updates, emdash updates, joint updates, and the
52
+ verification steps for each. Establishes the rule "update one
53
+ thing at a time, never both in the same deploy" so failure
54
+ bisects stay clean.
55
+ - CHANGELOG.md compatibility section calls out the verified emdash
56
+ range explicitly (`0.7.0` and `0.8.0`), names the upstream PR
57
+ (#734) that fixes the sandboxed-mode `content:afterSave` bridge
58
+ bug on `0.8.0`, and sets the convention for future releases to
59
+ record their tested range the same way.
60
+
9
61
  ## [0.1.0] - 2026-04-28
10
62
 
11
63
  First public release.
@@ -34,12 +86,21 @@ First public release.
34
86
 
35
87
  ### Compatibility
36
88
 
37
- - emdash `>= 0.7.0`
38
- - `@astrojs/cloudflare` adapter (Cloudflare Workers target). The
39
- plugin will silently noop on the Node adapter.
40
- - `vite-plugin-wasm` required in the consumer's `astro.config.mjs`
41
- vite plugins for the WASM import to resolve as a precompiled
42
- `WebAssembly.Module`.
89
+ - **emdash:** verified against `0.7.0` and `0.8.0`. Both modes work
90
+ on `0.7.0`; configured mode is recommended. On `0.8.0` the
91
+ sandboxed mode's `content:afterSave` hook also works (upstream
92
+ PR #734 wraps deferred plugin hooks in `after()` so bridge
93
+ bindings stay valid past the response — fixes the
94
+ silent-eval-skip behavior we worked around with the manual
95
+ Refresh button on `0.7.x`).
96
+ - **Adapter:** `@astrojs/cloudflare`. The plugin uses
97
+ Cloudflare-specific APIs and will silently no-op on the Node
98
+ adapter.
99
+ - **Vite plugin:** `vite-plugin-wasm` is required in the consumer's
100
+ `astro.config.mjs` for the WASM import to resolve to a
101
+ precompiled `WebAssembly.Module`. Not optional.
43
102
 
44
- [Unreleased]: https://github.com/schiste/Aexeo/compare/seogeo-plugin-v0.1.0...HEAD
45
- [0.1.0]: https://github.com/schiste/Aexeo/releases/tag/seogeo-plugin-v0.1.0
103
+ [Unreleased]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.2...HEAD
104
+ [0.1.2]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.1...aexeo-emdash-v0.1.2
105
+ [0.1.1]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.0...aexeo-emdash-v0.1.1
106
+ [0.1.0]: https://github.com/schiste/Aexeo/releases/tag/aexeo-emdash-v0.1.0
package/INSTALL.md CHANGED
@@ -117,15 +117,102 @@ compiles into the deployed Worker artifact.
117
117
 
118
118
  ## Updating
119
119
 
120
+ There are two things that can be updated independently — the plugin
121
+ package and emdash itself. **Update one at a time, never both in the
122
+ same deploy.** That way if something breaks, you know which side
123
+ introduced the regression.
124
+
125
+ ### Updating the plugin
126
+
127
+ For a routine patch / minor update within your existing semver range:
128
+
120
129
  ```bash
121
130
  npm update @aeptus/aexeo-emdash
122
- npm run build && <your deploy command>
131
+ npm run build
132
+ # Your usual deploy command
123
133
  ```
124
134
 
135
+ For a major version bump (e.g. from `^0.x` to `^1.x`):
136
+
137
+ 1. Read the [CHANGELOG](./CHANGELOG.md) entry for the new major.
138
+ Look for the `Compatibility` line that names the verified
139
+ emdash version range — confirm it covers your installed emdash.
140
+ 2. Bump the dependency range in `package.json`:
141
+ ```diff
142
+ - "@aeptus/aexeo-emdash": "^0.1.0",
143
+ + "@aeptus/aexeo-emdash": "^0.2.0",
144
+ ```
145
+ 3. `npm install`, build, deploy.
146
+
125
147
  The plugin's WASM is bundled with each version of the package, so
126
- `npm update` brings new rules along automatically. There's no
127
- separate sidecar to keep in sync (configured mode), no token to
128
- rotate, no Setup page to revisit.
148
+ `npm update` / `npm install` brings new rules along automatically.
149
+ There's no separate sidecar to keep in sync (configured mode), no
150
+ token to rotate, no Setup page to revisit.
151
+
152
+ **Verify after a plugin update:**
153
+
154
+ 1. Visit `/admin/plugins/aexeo-seogeo/findings` and click **Refresh**.
155
+ Toast should show non-zero rules sweeping. (A zero count *can*
156
+ be valid for a clean site, but if you previously had findings
157
+ and now have none, suspect the upgrade.)
158
+ 2. Save any document — `/findings` should auto-update for that
159
+ route's findings (the `content:afterSave` hook firing).
160
+
161
+ ### Updating emdash
162
+
163
+ Updating emdash is the more dangerous direction because emdash
164
+ controls the contracts the plugin depends on (PluginContext shape,
165
+ hook signatures, Block Kit schemas, capability strings, etc.).
166
+ Pre-flight before bumping:
167
+
168
+ 1. Look up the plugin version's tested emdash range in our
169
+ [CHANGELOG](./CHANGELOG.md). The latest plugin version's
170
+ `Compatibility` line says e.g. "tested against emdash 0.7.0
171
+ through 0.8.0." If the emdash version you want is **inside that
172
+ range**, the bump is safe. If it's **above the range**, treat it
173
+ as untested.
174
+ 2. If untested, scan the emdash release notes for breaking changes
175
+ in any of these areas (these are what the plugin touches):
176
+ - `PluginContext` shape (kv, http, content, log)
177
+ - Hook signatures (`content:afterSave`, etc.)
178
+ - Block Kit element schemas (banner variants, button styles,
179
+ table column formats)
180
+ - Capability validation strings (`read:content`, `network:fetch`)
181
+ - The `definePlugin` API
182
+ - The `createPlugin` / configured-plugin descriptor contract
183
+ If any of those changed, expect to bump the plugin too.
184
+ 3. Bump emdash:
185
+ ```bash
186
+ npm install emdash@<new-version>
187
+ ```
188
+ 4. Build and run locally first — never deploy directly to
189
+ production.
190
+ 5. Verify the plugin: same two checks as above (Refresh + save).
191
+
192
+ If verification fails: roll emdash back
193
+ (`npm install emdash@<previous-version>`), file an issue against
194
+ this plugin describing the failure, and stay on the prior emdash
195
+ version until a compatible plugin release ships.
196
+
197
+ ### When both need updating together
198
+
199
+ Sometimes a plugin release will require an emdash bump (because we
200
+ adopted a new emdash API). The plugin's CHANGELOG will say so:
201
+ "Requires emdash >= X.Y.0." When that's the case:
202
+
203
+ 1. Update emdash first.
204
+ 2. Verify the existing plugin still works on the new emdash.
205
+ 3. Update the plugin.
206
+ 4. Verify again.
207
+
208
+ Two separate deploys, two separate verification passes. This
209
+ keeps the bisect clean if anything breaks.
210
+
211
+ ### Compatibility quick-reference
212
+
213
+ The current verified pairing is recorded in
214
+ [CHANGELOG.md](./CHANGELOG.md) at the top of each release entry.
215
+ Treat anything outside that range as best-effort.
129
216
 
130
217
  ## Removing
131
218
 
@@ -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(_options?: ConfiguredPluginOptions): unknown;
10
+ export declare function createPlugin(options?: ConfiguredPluginOptions): unknown;
4
11
  export default createPlugin;
5
12
  //# sourceMappingURL=configured.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AA4EA,MAAM,WAAW,uBAAuB;CAIvC;AAED,wBAAgB,YAAY,CAAC,QAAQ,GAAE,uBAA4B,GAAG,OAAO,CAiC5E;AAID,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AA6EA,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAmD3E;AAID,eAAe,YAAY,CAAC"}
@@ -357,24 +357,33 @@ var inProcessEvaluator = async (documents) => {
357
357
  return { ok: false, reason: "wasm_error", detail };
358
358
  }
359
359
  };
360
- function createPlugin(_options = {}) {
360
+ function createPlugin(options = {}) {
361
+ const collections = options.collections === void 0 ? DEFAULT_COLLECTIONS : [...options.collections];
361
362
  return definePlugin({
362
363
  id: "aexeo-seogeo",
363
364
  version: "0.0.1",
364
365
  capabilities: ["read:content"],
365
366
  hooks: {
367
+ // afterSave processes one saved document at a time — collections
368
+ // list isn't needed here. The hook always runs regardless of
369
+ // which collection the document came from.
366
370
  "content:afterSave": (event, ctx) => handleAfterSaveConfigured(event, ctx, inProcessEvaluator)
367
371
  },
368
372
  routes: {
369
- admin: { handler: handleAdminRoute }
373
+ // The Refresh sweep (handleAdminRoute → handleRefresh) is the
374
+ // one consumer of `collections`. Bind the resolved list here so
375
+ // every request handles the same set without re-reading options.
376
+ admin: {
377
+ handler: (ctx) => handleAdminRoute(ctx, collections)
378
+ }
370
379
  },
371
380
  admin: {
372
381
  pages: [
373
- // emdash's /admin/plugins/<id> root navigates to the plugin's
374
- // "/" page — without it, clicking the plugin in the meta list
375
- // produces a "Not Found". Alias "/" to the findings page so
376
- // the entry-point lands somewhere useful.
377
- { path: "/", label: "SEO findings" },
382
+ // The root URL `/admin/plugins/<id>/` routes through the same
383
+ // dispatcher (handlePageLoad treats "" and "findings" as
384
+ // aliases). Listing "/" alongside "/findings" here would create
385
+ // duplicate "SEO findings" sidebar entries — that was the
386
+ // visible 0.1.0 / 0.1.1 bug fixed in 0.1.2.
378
387
  { path: "/findings", label: "SEO findings" },
379
388
  { path: "/document", label: "Document SEO" }
380
389
  ],
@@ -385,7 +394,7 @@ function createPlugin(_options = {}) {
385
394
  });
386
395
  }
387
396
  var configured_default = createPlugin;
388
- async function handleAdminRoute(ctx) {
397
+ async function handleAdminRoute(ctx, collections) {
389
398
  const body = ctx.input;
390
399
  if (body.type === "page_load") {
391
400
  return handlePageLoad(ctx, body.page);
@@ -395,7 +404,7 @@ async function handleAdminRoute(ctx) {
395
404
  return renderDocumentPanel(ctx, body.value);
396
405
  }
397
406
  if (body.action_id === "refresh_findings") {
398
- return handleRefresh(ctx);
407
+ return handleRefresh(ctx, collections);
399
408
  }
400
409
  if (body.action_id.startsWith("filter:")) {
401
410
  return renderFindingsPage(ctx);
@@ -426,11 +435,12 @@ async function handlePageLoad(ctx, page) {
426
435
  }
427
436
  return notFound(page);
428
437
  }
429
- async function handleRefresh(ctx) {
438
+ async function handleRefresh(ctx, collections) {
430
439
  let summary;
431
440
  try {
432
441
  summary = await evaluateAndPersistAll(ctx, {
433
- evaluator: inProcessEvaluator
442
+ evaluator: inProcessEvaluator,
443
+ collections
434
444
  });
435
445
  } catch (err) {
436
446
  const detail = err instanceof Error ? err.message : String(err);
package/dist/index.d.ts CHANGED
@@ -18,6 +18,27 @@ export interface ConfiguredPluginDescriptor {
18
18
  }[];
19
19
  }
20
20
  export interface SeogeoPluginOptions {
21
+ /**
22
+ * emdash collections the plugin sweeps when an admin clicks
23
+ * Refresh on the findings page. Order doesn't matter; the sweep
24
+ * iterates each collection sequentially and groups findings by
25
+ * document route. The per-document `content:afterSave` hook always
26
+ * runs regardless of this list — saving a document in any
27
+ * collection triggers an evaluation for that one document.
28
+ *
29
+ * Defaults to `["posts", "pages"]` (the slugs the
30
+ * `@emdash-cms/template-blog-cloudflare` template ships with).
31
+ * Override when your schema uses different collection slugs:
32
+ *
33
+ * seogeoPlugin({ collections: ["posts", "guides", "products"] })
34
+ *
35
+ * Pointing this at a slug that doesn't exist in your schema is
36
+ * non-fatal: the bridge's `content.list` returns empty and the
37
+ * sweep records the missing collection in the Refresh summary's
38
+ * `errors` field. Safe to ship a superset for a project that may
39
+ * add collections later.
40
+ */
41
+ collections?: readonly string[];
21
42
  }
22
43
  export declare function seogeoPlugin(options?: SeogeoPluginOptions): ConfiguredPluginDescriptor;
23
44
  //# sourceMappingURL=index.d.ts.map
@@ -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;IACnB,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;CAGnC;AAED,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,0BAA0B,CA0B5B"}
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;IACnB,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,CAsC5B"}
package/dist/index.js CHANGED
@@ -57,17 +57,27 @@ function seogeoPlugin(options = {}) {
57
57
  // time — must match the package.json `exports["./configured"]`
58
58
  // entry.
59
59
  entrypoint: "@aeptus/aexeo-emdash/configured",
60
- options: { ...options },
60
+ // emdash's astro integration JSON-serializes `options` and emits
61
+ // an importer that does `createPlugin(<options>)` at boot.
62
+ // Anything we put here is what the runtime entry's createPlugin
63
+ // receives. Spread to a plain object so readonly arrays survive
64
+ // the structured-clone semantics of that codegen.
65
+ options: {
66
+ ...options.collections === void 0 ? {} : { collections: [...options.collections] }
67
+ },
61
68
  capabilities: [
62
69
  "read:content",
63
70
  "read:schema",
64
71
  "kv:seogeo-baselines"
65
72
  ],
73
+ // adminPages drives the admin sidebar — one nav entry per item.
74
+ // Don't list "/" alongside "/findings": both would render as
75
+ // duplicate "SEO findings" links in the sidebar (0.1.0 / 0.1.1
76
+ // shipped with this duplicate; fixed in 0.1.2). The root URL
77
+ // /admin/plugins/<id>/ still routes correctly because the
78
+ // dispatcher in src/configured.ts treats "" and "findings" as
79
+ // the same page.
66
80
  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
81
  { path: "/findings", label: "SEO findings" },
72
82
  { path: "/document", label: "Document SEO" }
73
83
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeptus/aexeo-emdash",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Aexeo seogeo SEO/GEO content evaluator as an emdash plugin",
5
5
  "license": "MIT",
6
6
  "author": "Aexeo",